#Arrays, Slices and Pointers

1 messages · Page 1 of 1 (latest)

simple quest
#

Hello. It's day 2 of learning Zig and I'm stuck trying to understand a few things.
My background is C and C++.
In Zig, when I declare an array:
var arr: [5]u8 = undefined;
This is sort of the equivalent of this C code:
unsigned char arr[5];
In C, arr would now be a pointer to a block of memory that contains 5 unsigned char's.
But, in Zig, this is not the case? In Zig, what would arr be? And why do I need to do &arr to get the address of the start of the memory block?
It says that an Array in Zig is a type on it's own, and not like a pointer to the start of some memory, like in C. But the only "member" of an Array is it's length, so... where are the contents? &arr is where they are stored I guess, but if so, where is arr.len stored? This is a bit confusing for me so if someone could clarify it would be great.

Regarding Slices and Arrays in Zig, (the language reference has pretty much no text explaining the details of this, just code examples), is the only diference that the length is runtime and not complie time, and they are the same type, or are they different types? Some functions ask for Slices, other ask for whatever &arr returns, and it's all a bit confusing for me, I must be missing something.

#

Also, I don't quite get how the Pointers fit into all this, because in C and C++, it's all... Pointers 😅

radiant dome
#

In C, T[] decays to T*, in Zig, *[5]u8 coerces to []u8

#

In Zig, [5]u8 is a 5-byte object that is copied around by value unless you take the address

#

*[5]u8 is just a pointer, where the length of the underlying array is known at comptime from the type

#

[]u8 is like a struct containing a pointer to the array and the length of the array at runtime

simple quest
#

What is weird (not sure if this is some sort of syntax sugar) is, since in Zig Arrays are a object that has a len field, where is the field for the Array contents?

#

Do we "access that field" using the &arr?

radiant dome
#

for arrays and pointers to arrays, .len is like accessing what in C++ would be a static member, for slices .len is like accessing a struct field

#

it is syntax sugar because slices aren't actually structs

simple quest
#

What would the memory layout of an Array be? Something like <items><len>?

radiant dome
#

the len is part of the type, it's just n items with a stride of the size of the element

simple quest
#

Since the size is comptime, it would make sense why the &arr would work

radiant dome
#

there are no unknown-sized arrays in zig

simple quest
#

So the len must come first, else we would need to use generics for functions taking arrays of any size

radiant dome
#

there is no len stored

#

the compiler just looks at the type of the object you used .len on, and gives you that number directly

simple quest
#

Hmmm, so, If I have a function taking as a parameter an Array of any size, would It generate code for each type of array I pass the function, or how else would it know the size? len is part of the type, so how can I make in a function that doesnt know the size of the array I pass to it? Is that where Slices come in?

radiant dome
#

yes, slice is the only way to have a non-comptime-known length

#

and a slice can only be a pointer

simple quest
#

Okayy I think I'm undestarding... So .len on a Array is just syntax sugar to get the comptime known length, and on a Slice is, like you mentioned, like accessing a field in a Struct

radiant dome
#

yep, arr.len is the same as @typeInfo(@TypeOf(arr)).Array.len

simple quest
#

Okay okay, thank you for the patience, I appreciate the explanation! :)

#
    var input_buffer: [20]u8 = undefined;
    // input_buffer -> [20]u8
    _ = input_buffer.len;
    // syntax sugar to obtain the comptime known value
    // for the array size. It is equivalent to:
    _ = @typeInfo(@TypeOf(input_buffer)).Array.len;
    // to access the array contents:
    _ = &input_buffer; // *[20]u8
    // this gives us the address of the Array contents

    var slice_input_buffer = input_buffer[0..]; // some magic I don't understand yet
    _ = slice_input_buffer.len;
    // not syntax sugar, len is known at runtime
    // is accessing the len field of the Slice struct
    _ = slice_input_buffer.ptr; // [*]u8, because length information is lost in the type
    // this gives us the address of the Slice contents

Correct?

last quartz
#

"Slices are pointer arithmetic but with bounds checks, because they carry the length around with them", is a decent way to understand slices if you come from C.

#

The thing in C is that a fixed array T name[len]; is actually not a pointer.. but it does decay to a pointer.

#

So actually, you're doing &array in C too - just implicitly.

simple quest
last quartz
#

[You're actually doing &array[0] implicitly in C - but similar deal.]

last quartz
radiant dome
#

It's actually _ = slice_input_buffer.ptr; // [*]u8 because the length is no longer known

last quartz
#

^ This too

last quartz
#

ptr on a slice is like name in C, after it has decayed to a pointer.

#

That's actually what [*]T in Zig is meant to represent over just a *T.

#

That it points to an unknown number of Ts.

#

In this way, doing slice.ptr is analogous to doing an explicit decaying of the 'array' into a pointer, if you like.

#

[It's a slice not an array - but you get the idea.]

simple quest
#

So we have,
*T: pointer to a T
[*]T: pointer to unknown number of T's
*[20]T: pointer to 20 T's

#

Correct?

#

Besides constness, I think this covers all kinds of pointers

last quartz
#

Correct

radiant dome
#

yes, and []T pointer to a runtime-known number of T's

last quartz
#

^

#

And []T is the most common, because it's the most useful when referring to a block of something.

simple quest
last quartz
#

[*]T is the pointer-half of a []T, as it were.

radiant dome
#

[]T is runtime known length, [*]T is completely unknown length

last quartz
#

[]T is struct{ ptr: [*]T, len: usize }.

simple quest
#

So Slices are of type []T then?

last quartz
#
char buf[32];
char *ptr = &buf[0]; // this is really more of a [*]u8 in Zig
last quartz
#

Obviously, &array[0] is a *u8 - not a [*]u8 --- but that's the point; Zig makes a distinction between a pointer to one element, versus a pointer to a bunch of them - so you'd use a [*]u8 for ptr if you were doing stuff like this in Zig.

#

If you did stuff like ptr[3] in C, with that code, then ptr wants to be a [*]u8 in Zig.

#

This is made clear by the fact that you'd get a compile error if you tried to do that with a *u8.

last quartz
simple quest
#

So I can think of [*]T like C pointers, meaning, If I take a [*]T as a function parameter, I would need to pass in the number of elements, just like in C

last quartz
#

The logic generally is that * means pointer of some sort.
[] means multiple items of some sort.

last quartz
simple quest
#

I think I'm getting this, this is a very nice design actually

last quartz
#

Obviously, a slice points to its data, so it's kind of a pointer too -- but it's actually just a structure - whereas a pointer thing is specifically an integer memory address - kinda-deal.

last quartz
radiant dome
#

C pointers can either correpsond to *T or [*]T, depending on whether they are used in C to point to 1 or arbitrary many elements

simple quest
simple quest
last quartz
#

Right - you'll find they have .ptr too, IIRC.
The reason being that it is useful for generic programming - especially since Zig treats a *[N]T and a []T somewhat interchangably in most contexts.

radiant dome
#

no because you can't dereference a [*]T

last quartz
#

Yeah - you can't use [] on *T - and can't use .* on [*]T.

simple quest
#

(on a [*]T)

last quartz
#

Correct.

#

You'd have to use ptr[0] there.

simple quest
#

I see!
Thanks a bunch for explaining all this to me, I very much appreciate it! :D