Fields and Declarations and Types, oh my!
Having spent a little time absorbing all this, I thought I would reply back in my own “explain it to me like I’m a five year old” style in the hope I have understood things properly and that some other words might help someone else.
Zig structures have fields which serve as a template for how values and variables are lain out. But since Zig structures also serve the role of a container, they may have variable, constants and functions of their own. To know something about a field requires that a value or variable be the subject of the introspection. The structure type itself has no fields. None of this is conceptually unfamiliar to me, but clearly needed a kick-start of some ossified neurons to understand in the Zig context.
The suggestion of @castholm yields the answer in a concise use of three built-in functions. I works because:
undefined
may be coerced to any type.
@as()
yields a value of a given type even if the value is undefined
.
@TypeOf()
doesn’t care about the undefined nature of the value when it returns type information.
It’s a combination of reasoning I don’t think I would have arrived at on my own.
The suggetion of @AndrewCodeDev works off the same principle, namely to use @TypeOf()
to retrieve a field type from a variable whose value is undefined
. It is arguable a bit easier to understand for a less experienced Zig programmer.
Some time ago I was stumbling around in std.meta
and I took another look there to see if there was any help. In std.meta
there is another approach which uses a mapping onto the information returned by @typeInfo
. The function FieldEnum()
creates a type at comptime
that is an enumeration of the fields of a type. The generated type has enumerators whose names are the same as the fields and whose enumerated values are the ordinal position of the field information in the type information. So, the return value from @intFromEnum()
can be used directly as an index into the field information of the type. std.meta
also provides the functions fieldInfo()
and fieldType()
to perform the computation on the type information (along with some error checking).
After some experiments, I decided to use the functions in std.meta
because:
- The field may be specified by an enumeration literal which is mnemonic to the manner a structure initializer is specified.
- There is a well defined type to use as an argument.
- It’s part of the standard library and has a special preference because of that.
The rework of the original post using the functions in std.meta
is:
const std = @import("std");
const meta = std.meta;
const Info = struct {
name: []const u8,
age: u8,
};
const InfoStorage = struct {
const Self = @This();
storage: [30]Info,
const Fields = meta.FieldEnum(Info);
pub fn readField(
self: Self,
index: usize,
comptime field: Fields,
) meta.FieldType(Info, field) {
const storage_ref = &self.storage[index];
return @field(storage_ref, meta.fieldInfo(Info, field).name);
}
};
pub fn main() void {
var info: InfoStorage = undefined;
info.storage[0] = .{ .name = "Jane", .age = 42 };
// The next line compiles and runs fine.
std.debug.print("{d}\n", .{@field(&info.storage[0], "age")});
// This time we use an enum literal as an encoding for
// the field.
std.debug.print("{d}\n", .{info.readField(0, .age)});
}
Sorry for the long post and thanks again for your help.