Expected type '[][:0]const u8', found '[][:0]u8'

Hi all, I’ve got the following:

pub const Context = struct {
    const Self = @This();

    args: [][:0]const u8,

    pub fn init(allocator: Allocator) !Self {
        const args = try std.process.argsAlloc(allocator);

        return Self{ .args = args };
    }

    pub fn deinit(self: Self, allocator: Allocator) void {
        std.process.argsFree(allocator, self.args);
    }
};

test "create context" {
    const allocator = std.testing.allocator;

    const ctx = try Context.init(allocator);
    defer ctx.deinit(allocator);
}

which fails with the following error:
expected type '[][:0]const u8', found '[][:0]u8' on the line where I initialize Context with return Self{ .args = args };
I’ve read through the documentation and, in the reference it says the following (under Type Coercion: Stricter Qualification):

Values which have the same representation at runtime can be
cast to increase the strictness of the qualifiers, no matter
how nested the qualifiers are:
* const - non-const to const is allowed

So I believe this should work (and that if there’s a good reason this doesn’t work, the documentation should be updated to reflect that). I’ve tried using @constCast but, annoyingly, that only works for casting const to non-const.

Also, the error mentions this:
mutable '[:0]const u8' would allow illegal const pointers stored to type '[:0]u8'
which I’ve read over and over but I have no idea what it’s trying to say. Can someone smarter than me explain what this error actually means and how I can fix it?

Full error if needed:

test
└─ run test
   └─ zig test Debug native 1 errors
src/root.zig:9:23: error: expected type '[][:0]const u8', found '[][:0]u8'
        return Self{ .args = args };
                     ~^~~~~~~~~~~
src/root.zig:9:23: note: pointer type child '[:0]u8' cannot cast into pointer type child '[:0]const u8'
src/root.zig:9:23: note: mutable '[:0]const u8' would allow illegal const pointers stored to type '[:0]u8'

Can’t really explain the error, but note that std.process.argsFree takes []const [:0]u8 so you’d get a compile error there as well.

(I was going to suggest []const [:0]const u8, which avoids the error in the OP but then hits the error with argsFree)

1 Like

You can use

args: []const [:0] u8,

It will work.

I don’t know if I’m smarter than you, but i’ll give it a shot:

If you think of a slice as a struct .{.ptr = <address of start of memory>, .len = <size>}, where &slice[0] == slice.ptr, there are three ways a slice can be const:

  1. const []u8, meaning only the pointer and size field are const. The location and size of the data can’t change, but you’re allowed to mutate the data in place.

  2. []const u8, meaning the data itself is const. You can give the slice a new value by pointing it to a different block of memory, but you can’t mutate the data.

  3. const [] const u8, meaning both. The data is read-only and you can’t change the slice to point somewhere else.

I think what the error message is trying to convey, is that assigning a []u8 to []const u8 would allow reassigning the .ptr field in an unsafe way, so it isn’t allowed:

mutable '[:0]const u8'
“If the slice struct is mutable…”

would allow illegal const pointers stored
“… it is possible to assign a const value to .ptr…”

to type '[:0]u8'
“… which doesn’t match the underlying type.”

Why that’s a problem, I’m not sure about.

As @mnemnion mentioned, you can use args: []const [:0] u8, in your struct, that matches the return type of std.process.argsAlloc. If it’s important to you to enforce that the arg strings are const for some reason, I think you will need to copy them, but I’m not 100% sure on that. This works for me:

pub const Context = struct {
    const Self = @This();

    args: []const []const u8,

    pub fn init(allocator: std.mem.Allocator) !Self {
        const tmp = try std.process.argsAlloc(allocator);
        defer std.process.argsFree(allocator, tmp);

        var args = try allocator.alloc([]const u8, tmp.len);
        for (tmp, 0..) |arg, i| {
            args[i] = try allocator.alloc(u8, arg.len);
            std.mem.copyForwards(u8, args[i], std.mem.sliceTo(arg.ptr, 0));
        }
        
        return Self{ .args = args };
    }

    pub fn deinit(self: Self, allocator: std.mem.Allocator) void {
        for (self.args) |arg| {
            allocator.free(arg);
        }
        allocator.free(self.args);
    }
};

test "create context" {
    const allocator = std.testing.allocator;
    const ctx = try Context.init(allocator);
    defer ctx.deinit(allocator);
}

edit: sliceTo doesn’t allocate, old code would use-after-free.

2 Likes