How does passing slices as function parameters work internally in Zig?

Suppose there is the following Zig function

fn foo(comptime T: type, slice: []T) void {}

How is slice actually passed to foo in memory here?

Since slices in Zig are essentially pairs of a pointer and a length, I suppose slice’s type is internally similar to the struct

struct {
    buffer: [*]T,
    len: usize,
}

What I don’t know is how this struct is actually passed to foo. Are the two struct members passed to foo directly, i.e., foo is then equivalent to the following C snippet

void foo(T[] buffer, size_t len) {}

or are they passed indirectly to foo through a pointer pointing to the actual slice object, i.e., the equivalent C snippet is then

struct slice {
    T[] buffer;
    size_t len;
}
void foo(struct slice *slice) {
    T[] buffer = slice->buffer;
    size_t len = size->len;
}

instead?

Probably the most helpful answer is “you’re not allowed to ask”. Or rather, assume, ask whatever you’d like.

Zig’s internal calling convention is called .auto, and the explicit contract is that Zig does what it wants to. If you need a specific ABI, use export, that has the calling convention .c. But by the same token, an export fn cannot receive most Zig types, because those don’t have a guaranteed layout either. This includes slices.

If you just want to know what the compiler does, Godbolt is your friend. But don’t expect what you find to be stable in any sense.

3 Likes

Slices do not guarantee a specific memory layout, but in general, without optimization, they tend to follow the former.

1 Like