Converting an "array of slices" to a "slice of slices"

Hey there, I can’t work out how to pass a slice of slices to a function.

fn doSomething(slice_of_slices: [][]const u8) void {
    _ = slice_of_slices;
    //
}

test "array of strings" {
    const array_of_slices = [_][]const u8{
        "one",
        "two",
        "three",
    };

    doSomething(array_of_slices[0..]);
}

I’m getting this error:

error: expected type '[][]const u8', found '*const [3][]const u8'
    doSomething(array_of_slices[0..]);
                ~~~~~~~~~~~~~~~^~~~~

The way you’re passing the argument is fine (and &array_of_slices would also work), but the parameter type of doSomething is causing the issue: the type [][]const u8 is a mutable slice of []const u8s, but array_of_slices is const, so you can’t pass it to the function as-is. If doSomething doesn’t need to mutate slice_of_slices, then the parameter type can be changed to []const []const u8 (a constant slice of []const u8s).

5 Likes

Thanks for your help!
Coming from TypeScript-land it didn’t occur to me, I hadn’t understood the idea that types in Zig specify mutability. It appears this is only for slices :thinking: correct?

[]const is declaring that the slice is immutable, my dopey brain told me it was for the u8. . . . wow :joy:

1 Like

Still trying to wrap my head around the idea that types in the parameters of a function can be different to the types in the code, i’m not used to that.

e.g.

const array_of_slices = [_][]const u8{
        "one",
        "two",
        "three",
    };

As far as I know, I can’t use []const []const there in the code. So I guess slices aren’t something you can write directly, it has to be created from an array :thinking:

The const modifier occurs in the context of pointers, to indicate that the pointed to memory is constant. This is different from the mutability of the variable: for example,

const a: u8 = 111;
const b: u8 = 222;
var ptr: *const u8 = &a;
// This is legal:
ptr = &b; // Now the ptr variable points to b instead of a
// This is not legal:
// ptr.* = 123; // You cannot mutate the value pointed to by the pointer, since the pointer is const (this would be changing the value of b)

This is relevant to your situation because slices are fundamentally pointers: a slice is a pointer to some data (e.g. a [*]u8) along with the length of the data. If you have a slice s, you can access these parts using s.ptr and s.len, respectively. Returning to your example, the type []const []const u8 involves two levels of pointers, so const is specified in two places to indicate that both the outer pointer and inner pointer point to constant memory.

Now, arrays on the other hand are values, not pointers. When you use [_][]const u8, that is an array (with inferred length, _) of []const u8. Since arrays are not pointers, you can’t put const after the [_], since the mutability of the array contents is determined by whether the variable is declared var or const:

var a = [_]u8{0};
// This is legal:
a[0] = 1;
const b = [_]u8{0};
// This is not:
// b[0] = 1;

And the compiler knows this, so in the above example, the type of &a is *[1]u8 (pointer to a mutable array, since a is a var), and the type of &b is *const [1]u8 (pointer to a constant array, since b is a const).

Hopefully this makes sense and helps clarify what’s going on.

4 Likes

Oh, and I should also mention, it’s possible to get a slice directly in your code if you want (instead of an array):

const slice_of_slices: []const []const u8 = &.{
    "one",
    "two",
    "three",
};
4 Likes

Thanks so much, this is really helpful!

1 Like