Thanks! @FieldType did the trick - but it makes me wonder if my solution is a bit convoluted.
Here is my use case: I’m building a simple stack based on an array of union(enum) so I can store multiple types of data in it. The first version was very simple and straightforward: push and pop methods are taking/returning the union type.
const std = @import("std");
const Stack = struct {
array: [32]Data,
length: usize,
const init = Stack{
.array = undefined,
.length = 0,
};
const Data = union(enum) {
num: u8,
bool: bool,
};
fn push(s: *Stack, d: Data) !void{ ... }
fn pop(s: *Stack) !Data{ ... }
fn peek(s: *Stack) !Data{ ... }
}
It works, but it quickly gets verbose because every time I want to use something from the stack I have to pop and handle a possible underflow, and then unpack the data and handle a possible type mismatch.
To make it more concise, I wanted to have push/pop methods that packs/unpacks the data directly based on a tag argument - hence my question. The result is this:
const std = @import("std");
const Stack = struct {
array: [32]Data,
length: usize,
const init = Stack{
.array = undefined,
.length = 0,
};
const Data = union(enum) {
num: u8,
bool: bool,
};
const Tag = std.meta.Tag(Data);
fn TagType(comptime tag: Tag) type {
return @FieldType(Data, @tagName(tag));
}
fn push(s: *Stack, comptime tag: Tag, value: TagType(tag)) !void {
if (s.length == s.array.len) {
return error.StackOverflow;
}
s.array[s.length] = @unionInit(Data, @tagName(tag), value);
s.length += 1;
}
fn pop(s: *Stack, comptime tag: Tag) !TagType(tag) {
if (s.length == 0) {
return error.StackUnderflow;
}
const data = s.array[s.length - 1];
defer s.length -= 1;
if (std.meta.activeTag(data) != tag) {
return error.InvalidType;
}
return @field(data, @tagName(tag));
}
fn print(s: *Stack) void {
for (0..s.length) |i| {
std.debug.print("{any}\n", .{s.array[i]});
}
}
};
test "push pop ok" {
var s = Stack.init;
try s.push(.num, 23);
const v = try s.pop(.num);
try std.testing.expect(v == 23);
}
test "invalid type" {
var s = Stack.init;
try s.push(.num, 23);
try std.testing.expectError(error.InvalidType, s.pop(.bool));
}
test "underflow" {
var s = Stack.init;
try std.testing.expectError(error.StackUnderflow, s.pop(.bool));
}
Does it makes sense ? Am I bending backwards to solve this ?