#Arrays and Slices

1 messages · Page 1 of 1 (latest)

amber jackal
#

Hello Zig Community,
I just have a few questions when it comes to Zig's arrays and slices.

  1. May someone please explain what this &.{…} does? Is this taking a reference to an anonymous struct? When would I ever use this?

  2. I feel like there's two different syntaxes around how to declare a slice. For example:

var array = [5]i32{ 1, 2, 3, 4, 5 };
const slice = array[1..4];

slice is type of *[3]i32. Ok that's fine, but then if I do something like this:

var array = [5]i32{ 1, 2, 3, 4, 5 };
const slice: []i32 = array[1..4];

slice is now type of []i32. My question is, what's the different between *[3]i32 and []i32. Why does Zig have these different ways for declaring a slice?

  1. This question is very similar to question 2, but I wanted to break it up into its own question. How I read *[3]i32 is I have a pointer to an array of size 3 of type i32. Ok that makes sense, but going back to this example:
var array = [5]i32{ 1, 2, 3, 4, 5 };
const slice = array[1..4]; // We know that this is of type *[3]i32
print("{d}", .{slice[0]}); // Prints 2

slice[0] doesn't make sense to me. Coming from a C background, I would view *[3]i32 as a double pointer, so to me, this makes more sense print("{d}", .{slice.*[0]}); But clearly there is something that I am missing, may someone please explain why my thinking is incorrect?

  1. When trying to pass an array to a function, I have seen this:
fn foo(array: []i32) void {
    print("{any}", .{array});
}

pub fn main() void {
    var array = [5]i32{ 1, 2, 3, 4, 5 };
    foo(&array);
}

I don't understand why &array is declared like that. To me, it reads like I am passing in a double pointer to the function foo. Why is this the case? Is it just the syntax of language?

  1. Instead of doing &array, I have also seen array[0..]. array[0..] makes way more sense to me than &array. Is array[0..] the same as &array? Why would I choose one over the other?

Thanks!

sly topaz
wheat vine
#

I recommend reading documentation about slices and watching that video, but I have time so I can answer your questions directly as well.

Conceptually, a slice is a pointer + a length (two usizes). An array, on the other hand, is an entire copy of a bunch of the same item, owned by the variable itself. So if you have x: []u32, then @sizeOf(x) will probably be 2 * usize (although this is not guaranteed, slice layout is unspecified), while if you have x: [n]u32, then @sizeOf(x) = n * @sizeOf(u32).

  1. The syntax .{ } is also used for arrays: e.g. const x: [3]u32 = .{ 1, 2, 3 }. Doing &.{} can be used for pointer-to-array, which Zig can coerce to a slice (see #2).
  2. The two different ways are dependent on whether the "length" part of the slice is comptime-known or not. If it's comptime-known, it can be encoded as part of the type, so you only need to store the pointer at runtime, hence *[3]i32
  3. You're correct, but doing a[n] if a is of the type *[N]T will actually access the underlying array. This is kind of like how you can access fields of pointers-to-structs by doing .. It implicitly dereferences for you. This is also for the convenience of having *[N]T syntax be the same as []T, because they are conceptually the same: one is comptime-known length, the other is runtime-known.
  4. If you pass the array itself, it'll semantically be considered a copy of the whole array. Unlike in C, there is no array-pointer duality in Zig.
  5. They are the same. Both will give you a slice with comptime-known length, which is an array pointer
somber sparrow
sly topaz
#

.{ 1, @as(u8, 42) } coerces into [2]u64 just fine, even though the two types are comptime_int and u8

wheat vine
#

To be more of a pedant, if all elements can coerce to the specific element type you're casting it to

#

Get out-pedanted

sly topaz
#

big ackschually vibes
love it

amber jackal
#

Thanks for the long reply @wheat vine 🫶 So if I understand you correctly, when talking about slices, *[N]T is a comptime slice while []T is a runtime slice?

sly topaz
#

*[N]T isn't a comptime slice - it's a pointer-to-a-single-item, and that item just-so-happens to be an array

#

Zig has coercion rules to convert *[N]T to []T - a pointer to a sequence of items of compile-time-known length converts into a pointer + length pair to a sequence of items of runtime-knwon length

wheat vine
#

In source code, a pointer-to-array behaves almost syntactically identically to a slice whose length is comptime known. So, if you slice where both bounds are comptime known, you'll get *[3]i32.
For example, slices have a .ptr and .len so you can access the underlying pointer/length respectively. You can do this on pointer-to-arrays as well, for convenience

#

But only the length is comptime-known (known to be 3 in this case). The pointer can be runtime-known, and can change if you do var x: *[3]i32

amber jackal
#

So, if you have a slice where both bounds are known at comptime, you'll get *[N]T. This will get converted to a fat pointer []T at runtime?

sly topaz
amber jackal
#

Gotcha

#

So I am assuming that whenever possible, I should go with *[N]T cause it is known at comptime, where as []T will be determined at runtime?

sly topaz
#

it all depends on your use case...
if you're working with a specific length, say, a 3D vector, you'll work with [3]f64s and pointers to them.

if you want a sequence of items, of unset size, slices are the go-to option

#

sometimes you'll find yourself making arrays, of known set lengths, only to then be passed to functions (after a & ofc) as slices
this is quite common when you create a buffer array, for functions like std.fmt.bufPrint

amber jackal
#

I am not doing a good job explaining it, but I can see it