What is the differnce between &array and array[0..]

In the code below I am trying to consume user input and process it well enough for std.posix.execvpeZ. However, I am confused as to why both arguments[0..] and &arguments give exactly the same type:

const command_null: [*:0]const u8 = @ptrCast(command[0..command.len].ptr);
var arguments: [10:null] ?[*:0]const u8 =  undefined;
arguments[0] = @ptrCast(command[0..command.len].ptr);
var i: u8 = 1;
while (command_tokens.next()) |argument| {
    if (i >= 10) break;
    var ptr2: [*]u8 = @constCast(argument.ptr+argument.len);
    ptr2[0] = 0;
    arguments[i] = @ptrCast(argument[0..argument.len].ptr);
    i += 1;
}
arguments[i] = null;


std.debug.print("command {s} \n", .{command_null});
std.debug.print("arguments: {s} \n", .{arguments[0].?});
std.debug.print("arguments: {s} \n", .{arguments[1].?});
std.debug.print("arguments: {?*} \n", .{arguments[2]});

std.debug.print("arguments: {} \n", .{@TypeOf(arguments)});
std.debug.print("arguments: {} \n", .{@TypeOf(&arguments)});
std.debug.print("arguments: {} \n", .{@TypeOf(arguments[0..])});

const child_pid = std.posix.fork() catch |err| return err;
if (child_pid == 0) {
    std.posix.execvpeZ(command_null, arguments[0..], &envp) catch return 0;
}

The output of the last 3 prints is:

arguments: [10:null]?[*:0]const u8 
arguments: *[10:null]?[*:0]const u8 
arguments: *[10:null]?[*:0]const u8 

To be honest, I probably don’t exactly read well the pointer syntax and types in Zig, yet. so what I understand is that:

  1. [10:null] is an array of 10 elements. After the 10th elemnt there is null sentinel value.
  2. &T should give me an address of the first element, which in my understanding should be a pointer to the first ?[*:0]const u8.
  3. [0…] should be a slice pointer, so technically it should become something like *[10]?[*:0]const u8

I bet a pack of chocolate milk and a pack of crayons that I am wrong on both (2.) and (3.) . :slight_smile:

Can someone please explain?

I bet a pack of chocolate milk and a pack of crayons that I am wrong on both (2.) and (3.) .

Yeah, you are exactly right with that assumption.

  1. &T should give me an address of the first element, which in my understanding should be a pointer to the first ?[*:0]const u8.

&t will always give you a pointer to T *T, in this case *[10:null]?[*:0]const u8, to get a pointer the first element you’d use &t[0]

It works like this because Zig has a stonger type system than C and C++ which do not distinguish between an array pointer and single item pointer.

  1. [0…] should be a slice pointer, so technically it should become something like *[10]?[*:0]const u8

Yes, and in a way it does give you a slice, if you think about it a slice is a pointer and a length, now an array is a pointer with a comptime-known length, so you could argue that an array-pointer is a slice of comptime-known length.
And you’ll also find that an array can be directly coerced to the equivalent slice, so it can be inserted into any functions that want the equivalent slice. However in this case the equivalent slice would be [:null]?[*:0]const u8, so if you do
const argSlice: [:null]?[*:0]const u8 = &arguments
you can verify that it does convert to a slice without a problem.

Furthermore watch the terminology a slice is already a pointer, so saying “slice pointer” would technically be a pointer to a slice *[]T.

Furthermore, looking at your code, you seem to have an awful amount of ptrCasts and even worse a constCast in there. What are the types of command and command_tokens.next?
Specifically the following seems quite dangerous:

var ptr2: [*]u8 = @constCast(argument.ptr+argument.len);
ptr2[0] = 0;

You are writing beyond the length of the buffer. This is not safe at all, and could overflow into the next argument, or whatever allocation comes next in memory.
If you want to add a null terminator it is best to use allocator.dupeZ() to avoid any problems from this. This should prevent all the pointer casts.

Also note that command[0..command.len] is a no-op. It should be equivalent to just command.

8 Likes

Thank you for the explanation. It still feels very foreign to me but your point about C hit home. I am also still acustoming to the notation. And the difference between [n], *[], [*] and [] doesn’t come easily to me.

This makes a lot of sense. It seems related to the docs:

 // If you slice with comptime-known start and end positions, the result is
    // a pointer to an array, rather than a slice.

Okay so without coersion it would be an array pointer but with coersion it can become a slice. In a sense seems like array pointer is preffereble to the slice. Maybe because of its comptime’ness?

I am trying to implement a shell to learn Zig. This piece of code is supposed to transform a [1024]u8, which contains the user input, into a [*:null]const ?[*:0]const u8 required by execvpeZ. Originally I got inspired by a PR for Zigish but desided to implement it in my own way and learn from the mistakes I will make along the way.

I am curious about how to do that without using the heap, but I agree that copying would be way more steight forward. Thansk for the suggestion.

If the allocator is an std.heap.FixedBufferAllocator, you can do allocator.dupez on the stack.

1 Like

example

    var buf = [_]u8{0} ** 100;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    const ally = fba.allocator();
    const data = "asdfasf";
    const duped = ally.dupeZ(u8, data) catch unreachable;
    std.debug.print("{s}", .{duped});

its called heap, cause its heaps of unecissary or something :stuck_out_tongue:

1 Like

I still have to use memory for that. If I do the trick from ziggish then I just don’t have have extra memory. Good to know about the FixedBufferAllocator though. Thanks.

Does anyone know why:

var arr: [10]u8 = undefined;
var x = 0
_ = &x;
try expectEqual(&arr[x..] == *const []);

Why does the &arr[x..] produce a constant when either & or [x..] is not constant on it’s own?

You are taking the address of a temporary. It’s one of the more obscure features of Zig.
You can take the address of any temporary value, like e.g. &(x + 1) and the compiler will put the value on the stack and give you a const pointer to that value.
So for &(x + 1) you’d get something like *const usize.
In your case &arr[x..] the compiler places arr[x..] which is of type []u8 on the stack and gives you a const pointer pointer to that value, which is then of type *const []u8. (Mind that this is now a double pointer)

1 Like

Thank you. It started to make sense but then I tried to add to that temporary value:

var x: u32 = 1;
_ = &x;
var m = &(x+1);
m.* += 1;

And got:

main.zig:13:6: error: cannot assign to constant
    m.* = m.* + 1;

My understanding is that it is an area in the stack and that the pointer to it can’t be changed. Then why does the dereferenced value can’t be changed?

I should then also be able to create anonymou arrays then right?

My understanding is that it is an area in the stack and that the pointer to it can’t be changed. Then why does the dereferenced value can’t be changed?

The pointer to it can be changed (you made it a var after all). Just the temporary value itself can’t be changed. It’s a *const, pointer to const.
The reason why the value itself cannot be changed is to allow for more optimizations (the compiler is allowed to put it into constant memory if the value is comptime known).

I should then also be able to create anonymou arrays then right?

This seems unrelated, but no you cannot create anonymous arrays, since the compiler will always interpret it as an anonymous tuple if you do .{...}.

This is very helpful to me thanks.

What would a good strategy in your opinion to learn this? What worked for you?

One tip from me would be to use @compileLog(variable-or-constant) when you are unsure about the types of the variables or constants you are dealing with to print their actual types.

Then over time learn to read types exactly and what they mean, because having a precise understanding about the types makes most error messages more understandable.

Other than that I am not sure what you expect other than the answers you already got, I guess I would add write code and work on small projects.

With Zig’s types, conversions, etc. I also think it is something you tend to discover new things about as you continue to use it. Reading other peoples code or code in the standard library also helps exposing you to things that are still unfamiliar to you.

3 Likes

Yeah, didn’t word it exactly. I am somewhat confused by the documentation at times, find it difficult to find answers there. I read through a big chunk of reference but a lot of time when I want to find something I still struggle. So I am not sure if I am doing it wrong.

You answer helps though. Thanks!

1 Like

You always can ask additional questions once you have a suspicion that you might have a misunderstanding about how something works.

1 Like