Struct with comptime fields refuses to coerce to struct with runtime fields

It seems like structs with only comptime stuct fields can coerce to compatible structs:

@compileLog(@as(Rational(i32), .{.num = 6, .den = 9}));
@compileLog(@TypeOf(.{.num = 6, .den = 9}));
Compile Log Output:
@as(types.Rational(i32), .{.num = 6, .den = 9})
@as(type, struct{comptime num: comptime_int = 6, comptime den: comptime_int = 9})

So I attempted to make use of this for obvious purposes:

fn ComptimeRationalFromString(comptime xstr: []const u8) type {
    const sign, const str = if (std.mem.startsWith(u8, xstr, "-")) .{ -1, xstr[1..] } else .{ 1, xstr };

    if (str.len == 0) return @TypeOf(comptimeRational(0, 1));

    const dec_idx = (std.mem.indexOfScalar(u8, str, '.') orelse str.len - 1) + 1;

    if (dec_idx == str.len) return struct {
        comptime num: comptime_int = parseComptimeInt(str, 10),
        comptime den: comptime_int = 1,
    };

    const num = parseComptimeInt(str[0..dec_idx - 1], 10);
    const scale = std.math.powi(u65535, 10, str.len - dec_idx) catch |e| @compileError(@errorName(e) ++ ", what?");
    const den = parseComptimeInt(str[dec_idx..], 10);

    return struct {
        comptime num: comptime_int = num * scale + den,
        comptime den: comptime_int = scale * sign,
    };
}

pub fn comptimeRationalFromString(comptime str: []const u8) ComptimeRationalFromString(str) {
    return .{};
}

But it doesn’t work:

error: expected type 'types.Rational(i32)', found 'types.ComptimeRationalFromString("0.64")'

Why doesn’t it, and what do I need to do to make it or something similarly ergonomic work?

        pub const bt_2020 = .{
            cRS("0.708"), cRS("0.292"),
            cRS("0.170"), cRS("0.797"),
            cRS("0.131"), cRS("0.046"),
        };

Ought to coerce to [6]struct{num: i32, den: i32}, right?

I believe this is incorrect, but it is a very understandable mistake to make.

I could be wrong on the exact details, but as I understand it, an anonymous struct literal like const foo = .{ .a = 1, .b = 2 } without a known result type is currently considered by the compiler to be an instance of an “anonymous struct type”. Anonymous struct types are special in that they allow coercions based on structural equivalence that regular structs types do not.

Builtins like @compileLog(.{ .a = 1, .b = 2 }) will print the type as struct { comptime a: comptime_int = 1, comptime b: comptime_int = 2 }, but it is still an instance of an anonymous struct type and not the same thing as an instance of the concrete (unnamed) struct type struct { comptime a: comptime_int = 1, comptime b: comptime_int = 2 }:

const Foo = struct { a: i32, b: i32 };

pub fn main() void {
    // instance of anonymous struct type
    @compileLog(.{ .a = 1, .b = 2 });
    // instance of Foo
    @compileLog(@as(Foo, .{ .a = 1, .b = 2 }));
    // instance of main.main_struct_1234
    @compileLog(@as(struct { comptime a: comptime_int = 1, comptime b: comptime_int = 2 }, .{ .a = 1, .b = 2 }));
    // this is a compile error
    @compileLog(@as(Foo, @as(struct { comptime a: comptime_int = 1, comptime b: comptime_int = 2 }, .{ .a = 1, .b = 2 })));
}

The core team seems to agree that this is confusing, and there is a related accepted proposal for removing anonymous struct types: Remove anonymous struct types from the language · Issue #16865 · ziglang/zig · GitHub

So what you are trying to accomplish is unfortunately not possible using structs with comptime fields and you may need to try a different approach.

5 Likes

Thank you for your detailed explanation! It was a joy to read. I “resolved” it by writing a (non-generic) function that converts it to runtime explicitly, which works for my narrow use-case:

pub fn runtimeRationalFromString(comptime str: []const u8) Rational(i32) {
    const crs: ComptimeRationalFromString(str) = .{};
    return .{ .num = crs.num, .den = crs.den };
}
1 Like