I’ve been learning zig, and while I’ve been able to understand it’s quirks, especially around comptime, this one has me confused.
Can anyone tell me why this is allowed:
const A = struct {
a: u32 = 0,
b: u32 = 0,
c: u32 = 0,
d: u32 = 0,
};
// This function compiles fine
fn set_by_index(s: *A, index: u32, value: u32) void {
const Ti = @typeInfo(A).@"struct";
inline for (Ti.fields, 0..) |f, i| {
if (i == index) {
@field(s, f.name) = value;
}
}
}
While this is not allowed:
// error: values of type 'builtin.Type.StructField' must be comptime-known, but index value is runtime-known
// const f = Ti.fields[index];
// ^~~~~
//
fn set_by_index_bad(s: *A, index: u32, value: u32) void {
const Ti = @typeInfo(A).@"struct";
const f = Ti.fields[index];
@field(s, f.name) = value;
}
I came about this looking for a better/faster method of accessing a struct field by name at runtime. I’m sure everyone’s seen the classic “inline loop over the struct fields with mem.eql“:
// Just loops over the fields...
fn set_by_name(s: *A, fname: []const u8, value: u32) void {
const Ti = @typeInfo(A).@"struct";
inline for (Ti.fields) |f| {
if (std.mem.eql(u8, f.name, fname)) {
@field(s, f.name) = value;
}
}
}
While this is the best I’ve come up with as an improvement:
// It still loops over the fields, but by checking the index, not doing a string compare
// hopefully it is optimized into a lookup at compile time
fn set_by_name_faster_maybe(s: *A, fname: []const u8, value: u32) void {
const Ti = @typeInfo(A).@"struct";
// setup static string map
comptime var map_data: [Ti.fields.len]struct { [:0]const u8, usize } = undefined;
inline for (Ti.fields, 0..) |f, i| {
map_data[i][0] = f.name;
map_data[i][1] = i;
}
const map = std.StaticStringMap(usize).initComptime(map_data);
const idx = map.get(fname) orelse return;
set_by_index(s, @intCast(idx), value);
}
Does anyone have any better/cleaner ways to do this?