Cannot implicitly cast double pointer?

Why can’t I perform this cast?

How do I perform this cast?

test "double pointer cast to anyopaque" {
    var my_strings: []const []const u8 = &.{ "hello", "world" };
    const my_ptr: ?*anyopaque = &my_strings;
    _ = my_ptr;
}
src/argparser/test.zig:288:33: error: expected type '?*anyopaque', found '*[]const []const u8'
    const my_ptr: ?*anyopaque = &my_strings;
                                ^~~~~~~~~~~
src/argparser/test.zig:288:33: note: cannot implicitly cast double pointer '*[]const []const u8' to anyopaque pointer '?*anyopaque'

I need to do this because I am attempting to build a struct that has a default value for a []const []const u8 field using @Struct:


pub const Argument = struct {
    field: std.builtin.Type.StructField,
    count: Count,
    help: []const u8 = "",
    short: ?u8 = null,

    pub const Count = enum { single, unlimited };

    /// For `.count = .unlimited` arguments, the provided `T` must be a slice.
    pub fn init(
        comptime T: type,
        options: struct {
            name: [:0]const u8,
            count: Count = .single,
            help: []const u8 = "",
            default_value: ?T = null,
            short: ?u8 = null,
        },
    ) Argument {
        switch (options.count) {
            .single => {},
            .unlimited => {
                if (@typeInfo(T) != .pointer and @typeInfo(T).pointer.size != .slice) {
                    @compileError("multi-arguments must be a slice.");
                }
            },
        }
        return .{
            .field = .{
                .name = options.name,
                .type = T,
                .default_value_ptr = if (options.default_value) |value| &value else null,
                .alignment = null,
                .is_comptime = false,
            },
            .count = options.count,
            .help = options.help,
            .short = options.short,
        };
    }
};
src/argparser/root.zig:38:73: error: expected type '?*const anyopaque', found '*const []const []const u8'
                .default_value_ptr = if (options.default_value) |value| &value else null,
                                                                        ^~~~~~
src/argparser/root.zig:38:73: note: cannot implicitly cast double pointer '*const []const []const u8' to anyopaque pointer '?*const anyopaque'
src/argparser/test.zig:230:26: note: called at comptime here
                    .init([]const []const u8, .{ .name = "files", .count = .unlimited, .default_value = &[_][]const u8{ "README.md", "build.zig" } }),
                    ~~~
test "double pointer cast to anyopaque" {
    const my_strings: []const []const u8 = &.{ "hello", "world" };
    const my_ptr: ?*anyopaque = @ptrCast(&my_strings);
    _ = my_ptr;
}

(notice that the default value for @Struct is actually of type *const anyopaque, so my_strings can be const.)

what the compile error is telling you is that the double pointer won’t implicitly cast; it doesn’t prevent you from explicitly casting it. one reason this is useful is to prevent a class of errors arising from overoptimistically including & everywhere.

1 Like

It won’t ever implicitly cast because a many item pointer cannot coerce to an anyopaque pointer.

You can cast a single item pointer only.

.default_value is supposed to work for any type T, for eample bool. Does using @ptrCast reduce the safety of my init function?

yes, nothing i have said contradicts .default_value working for any type. is there some reason you think otherwise that i can help with?

also, no need to be scared of @ptrCast here, imo. since you’re working at comptime, any problems you run into will be compile errors anyway.

yeah you’re right, because .default_value is of type ?T then the compiler will force it to match T so I should safe to @ptrCast in all cases.

final implementation for those curious


pub const Argument = struct {
    field: std.builtin.Type.StructField,
    count: Count,
    help: []const u8,
    short: ?u8,

    pub const Count = enum { one, unlimited };

    /// For `.count = .unlimited` arguments, the provided `T` must be a slice.
    pub fn init(
        comptime T: type,
        comptime options: struct {
            name: [:0]const u8,
            count: Count = .one,
            help: []const u8 = "",
            default_value: ?T = null,
            short: ?u8 = null,
        },
    ) Argument {
        switch (options.count) {
            .one => {},
            .unlimited => {
                if (@typeInfo(T) != .pointer and @typeInfo(T).pointer.size != .slice) {
                    @compileError("unlimited arguments must be a slice.");
                }
            },
        }
        return .{
            .field = .{
                .name = options.name,
                .type = T,
                .default_value_ptr = if (options.default_value) |value| @ptrCast(@alignCast(&value)) else null,
                .alignment = null,
                .is_comptime = false,
            },
            .count = options.count,
            .help = options.help,
            .short = options.short,
        };
    }
};

i believe the options to init must be marked comptime as well, tho. at least, if you’re eventually feeding Argument to @Struct

1 Like

Yeah the comptimeness was getting propagated because much later in the call stack I had a @Struct() call based on a series of Arugment, but yeah I agree its best to explicitly declare comptime here because it explains why I can return a pointer to a stack variable :slight_smile:

1 Like