I’m working on an application where I have a tagged union, and each of the tagged union members are structs with multiple, differently named fields. I would like to implement an iterator function such that callers can iterate over the fields in a particular struct. Ideally I don’t want to implement something different for ever struct in the union, and also I’d like it to either automatically pick up new fields (if they’ve been added), or at least give me a compile error so I know to update the code.
To make the problem more concrete, my code looks something like this:
pub const Instruction = union(InstructionName) {
define_method: struct {
out: *Operand,
name: *Operand,
func: *Operand,
},
getlocal: struct {
out: *Operand,
in: *Operand,
},
getself: struct {
out: *Operand,
},
jump: struct {
label: *Operand,
},
jumpunless: struct {
in: *Operand,
label: *Operand,
},
const OpIter = struct {
idx: usize, // maybe the index of the struct field?
op: t,
pub fn next(self: *@This()) ?*Operand {
// Some kind of comptime switch plus use of `self.idx`?
}
};
pub fn opIter(self: @This()) OpIter {
// How can I implement this?
return .{ .idx = 0, .op = self };
}
};
const iter = some_insn.opIter();
while (iter.next()) |operand| {
// ...
}
I have an idea for how to implement opIter, but it feels very naive. I was thinking if I could possibly map an integer to each field in each struct so that the iterator context would know “where it is” on each call to next(). This feels possible at comptime, but I can’t figure out how.
I know I could manually write out each case, but I feel like there must be a better way and I’m not confident enough in Zig to know whether or not that is true.
I guess an important question is whether you want it at comptime or at runtime? If you need runtime iteration, then you need to somehow solve the problem that all fields’s types are different.
If comptime is ok, then probably you don’t need an iterator here, and need to return a slice of fields?
EDIT: ah, of course all fields have the same type in your example!
I’ll need to do the iteration at runtime since I can’t know which instruction I’m dealing with at compile time.
I was thinking, at comptime I could map integers to struct members in some kind of switch so that at runtime I could switch in the current “index”.
I know that I can get a list of fields for a particular struct at comptime (including the index) so I was hoping I could somehow generate a switch statement to map the index to that particular field in the struct.
For any particular struct I know I could do something like:
Yeah, this is hard, because you need to jump between comptime and runtime a couple of times.
Getting field at comptime is easy: @field(value, "fieldname"), where "fieldname" is a comptime thing. But you have a runtime index. To go from runtime to compile value, you need to “write” code like
switch (runtime_int) {
0 => /* hey, now it is comptime 0 */
}
which is achieved by using one of the inline constructs — either inline for, or inline else in a switch.
Here, we do it by inline for ing all struct fields.
But then you have a different problem — you also need comptime type of you struct, but this type is determined by runtime discriminant of the union. So, again, we have a pattern where we need to go from runtime world into comptime world, and this time we do that by inline elseing the enum!
It looks to me like all fields of the union are just arrays of *Operand, that just happen to have different lengths and names for each element of the array. In that case, your iterator can just be a slice of *Operand:
Thank you for the explanation. I was kind of getting there, but the multiple jumps between comptime and runtime was throwing me for a loop (pun intended, but also the truth)
I actually think a more C-like approach would work better here:
const InstructionType = enum{
defineMethod,
getLocal,
//... others
fn operandCount(self: @This()) usize{
return switch(self){
.defineMethod => 3,
.getLocal => 2,
// others...
};
}
};
const Instruction = struct{
instructionType: InstructionType,
/// This requires the user to know how many operands a specific
/// instruction type will operate on, but we can solve that with
/// a convenience method
operands: [3]*Operand,
/// For extra safety, you add this conveniece.
pub fn getOperands(self: *const @This()) []const *Operand{
const len = self.instructionType.operandCount();
return self.operands[0..len];
}
};