Type resolution for std.builtin.Type.StructField

Is this supposed to work? I know there were a lot of changes to type resolution recently, so I figured I would try this again. It didn’t work before and it still doesn’t work. Its very similar to std.builtin.Type.StructField. Should it work?

pub const Argument = struct {
    name: [:0]const u8,
    type: type,
    default_value: @TypeOf(@This().type),
};

test {
    const my_arg: Argument = .{
        .name = "config-file",
        .type = []const u8,
        .default_value = "",
    };
    _ = my_arg;
}

zig test src/cli/test.zig 
src/cli/test.zig:4:35: error: struct 'test.Argument' has no member named 'type'
    default_value: @TypeOf(@This().type),
                           ~~~~~~~^~~~~
src/cli/test.zig:1:22: note: struct declared here
pub const Argument = struct {
                     ^~~~~~
referenced by:
    test_0: src/cli/test.zig:8:5

(yes this is a thinly veiled nerd snipe on mlugg)

you are trying to access the value of a field during the structs definition! how do you think it would work?

Sorry I can’t answer your question but for my own education, why not use a comptime parameter in that particular case? Maybe you have a more complicated use case where this it not possible.

my_arg.type is comptime only, so the compiler knows (at some point during the compilation) what my_arg.type is, so I figured I could use it?

well actually no, This() references the struct definition, not the instance of the struct…so I guess I don’t know what I expected to happen here.

You want the type for default_value to change depending on the future value of another field on and per instantiation.

It wouldn’t ever work with the syntax you used, and I don’t know why zig would ever support this. What utility does it have that a type returning function couldn’t fulfil.

ah so this is what I want

pub fn Argument(T: type) type {
    return struct {
        name: [:0]const u8,
        default_value: T,
    };
}

test {
    const my_arg: Argument([]const u8) = .{
        .name = "config-file",
        .default_value = "",
    };
    _ = my_arg;
}
2 Likes

well the whole point is that I want to throw around types like they are values, and it kind of defeats the purpose if I have to do this:

const std = @import("std");

pub fn Argument(T: type) type {
    return struct {
        name: [:0]const u8,
        default_value: T,
        type: type = T,
    };
}

test {
    const my_arg: Argument([]const u8) = .{
        .name = "config-file",
        .default_value = "",
    };
    try std.testing.expectEqual([]const u8, my_arg.type);
}

It doesn’t defeat the purpose, it’s just less concise than you would like.

Unless you have a use case that can’t be solved by functions.

You’re trying to access a field as if it was a declaration.

What you’re doing is equivalent to having a Foo = struct { bar: i32 } and then attempting to access Foo.bar it’s nonsensical. You’ve just obfuscated it by using @This, @TypeOf and by placing the access within the definition of the struct.

Also consider this: what should @sizeOf(Argument) be?

1 Like

You can do something like this:

This post is also somewhat related and interesting How to construct a tuple at compile time using a for loop? - #5 by mlugg

Whether you should use these possibilities is another question…

2 Likes

A field type is not allowed to depend on the value of another field, and it’s extremely unlikely that this ever changes. You used to be able to sorta do this with something called “anytype fields”, but they were removed when we transitioned to the self-hosted compiler back in 0.10.0, I believe because they were a pretty janky language feature with insufficient use cases to justify their complexity.

That’s why std.builtin.Type.StructField does what it does with an anyopaque pointer. If you need this functionality in your own code, I recommend copying its approach. Of course, you can use an initializer function to avoid the slight awkwardness of initializing that pointer. I’m sure you can figure out what that’d look like, but for completeness, here’s how you could write something like your original example:

pub const Argument = struct {
    name: [:0]const u8,
    Type: type,
    /// Must be a valid `*const Type`. See `defaultValue` for a convenient accessor.
    default_value_ptr: *const anyopaque,

    /// `inline` to ensure return value is comptime-known.
    pub inline fn defaultValue(comptime a: Argument) a.Type {
        const ptr: *const a.Type = @ptrCast(@alignCast(a.default_value_ptr));
        return ptr.*;
    }

    pub fn init(
        comptime name: [:0]const u8,
        comptime Type: type,
        comptime default_value: Type,
    ) Argument {
        return .{
            .name = name,
            .Type = Type,
            .default_value_ptr = &default_value,
        };
    }
};
8 Likes