I was surprised to find that the following code fails to compile:
test "std.meta.fieldNames" {
const X = struct { a: u32 };
const x: X = .{ .a = 0 };
const name = std.meta.fieldNames(X)[0];
std.debug.print("{}\n", .{@field(x, name)});
}
with a error: unable to resolve comptime value.
Meanwhile, either of these would work fine:
test "std.meta.fieldNames" {
const X = struct { a: u32 };
const x: X = .{ .a = 0 };
std.debug.print("{}\n", .{@field(x, std.meta.fieldNames(X)[0])});
}
test "std.meta.fieldNames" {
const X = struct { a: u32 };
const x: X = .{ .a = 0 };
const name = comptime std.meta.fieldNames(X)[0];
std.debug.print("{}\n", .{@field(x, name)});
}
It’s unfortunate because it means that if I do an
inline for (std.meta.fieldNames(X)) |name| {
...
}
then name is not comptime-known in the block. That seems counterintuitive to me, especially considering the case of the analogous inline else => |_, tag| inside a switch statement.
If you change the implementation of std.meta.fieldNames to be an pub inline fn instead, it works as I expected. But I am surprised it’s necessary.
test "std.meta.fieldNames" {
const X = struct { a: u32 };
const x: X = .{ .a = 0 };
const name = @typeInfo(X).@"struct".fields[0].name;
std.debug.print("{}\n", .{@field(x, name)});
}
also succeeds, which is even more surprising, given that (from its implementation) std.meta.fieldNames is doing nothing more than assembling into an array the memory locations of the names of the fields of the typeInfo above, and returning a *const to that array.
Oh great! I didn’t think to try that. But can you explain why the error? It seems that assigning the const name has demoted a comptime-known value. Is this a general property of assignments outside comptime blocks?
The reason your pub inline fn change made it work without the comptime keyword is because comptime args can then propagate the result at comptime (per the language reference) - const’s can be runtime-known, it’s just that you can’t reassign.
Right, I know that const values can be runtime known, but somehow I managed to form the (I now think false) impression that const assignments of comptime-known expressions would always also be comptime-known.
I guess the rule is, they are only comptime-known if assigned in a comptime context, which is implicit if their type is comptime-only but otherwise you have to specify.
Does that sound right?
The reason for my mistaken impression was precisely that you don’t have to specify comptime when the expression you’re assigning has a comptime-only value!
That is actually true: if what you assign to the const variable is comptime known, then so is the const variable. In your example, for the expression to be comptime known you need to either prefix it with comptime or make the function inline, as you noticed.
Somewhat related, if you look at the implementation of fieldNames, you’ll see this:
I usually use inline for(std.meta.fields(T)) |f| something(f.name, ...) it also allows accessing the type via f.type (and other fields that may be useful), which means that f has to be comptime-known because it contains a type.
Ah, I think I understand now.
The point is that (non-inline) function calls will not propagate comptime-known-ness unless they’re called in a comptime context (which is implicit if their return type is comptime-only, otherwise not).
fn clone(x: anytype) @TypeOf(x) {
return x;
}
test "a-ha" {
const name = clone("a");
const X = struct { a: u32 };
const x: X = .{ .a = 1234 };
std.debug.print("{}\n", .{@field(x, name)});
}
fails to compile.
Whereas, if I inline the function, or put a comptime at the call-site, it works as expected.