Hi,
I’m working on learning zig by implementing a web-form application. In it I have components that are parsed from the url.
In my first implementation these components were handled using an enum and some switches for choosing implementation. The parsing was simply done with a string to enum:
fn parse_component(path: []const u8) !Component {
var path_iter = std.mem.tokenizeAny(u8, path, "/");
_ = path_iter.next(); // dump the first item in the path
_ = path_iter.next(); // dump the id part of the path
if (path_iter.next()) |component_str| {
const component = std.meta.stringToEnum(Component, component_str);
return component orelse .unknown;
}
return UrlError.ComponentTokenNotFound;
}
As I now have more than two components and will add 12ish more in the future I felt that this is a good case for using an interface, and thought that implementing that with a union would be simple:
pub const Component = union(enum) {
info: Info,
attributes: Attributes,
conditions: Conditions,
unknown,
pub fn get(self: Component, id: u32, allocator: Allocator, state: State) []const u8 {
switch (self) {
inline else => |s| s.get(id, allocator, state),
}
}
pub fn put(self: Component, data: anytype) void {
switch (self) {
inline else => |s| s.put(data),
}
}
};
At this point I ran into a problem as my parser no longer worked. I found @unionInit
which does what I need, but requires a comptime string, which
I don’t have.
I don’t see how to solve this without moving the enum declaration out of the union, which would add an extra place to keep up to date, or doing the same with the parser.
Is there a simple way to do this?
This is a good use for StaticStringMap. You can initialize it at comptime with an always-fresh version of the enum, and use it to map from the enum’s string representation to its enum value at runtime.
I did this with an assembler for a VM I’m working in, it does the trick.
If I understand you correctly, then comptime build a map from the tagnames in the enum to the relevant implementation for that tag. Then in the parser I can just return the implementation for whatever one matches.
If so, how can I access the underlying enum in the union, or do I need to define that separately?
Correct.
Tagged unions coerce to their enum value, so any time you use them in a way which demands an enum, you’ll get an enum.
I’ve found it valuable to define the enums of a tagged union separately from the union, but it shouldn’t be necessary.
std.meta has a function tags
, which you might find useful.
1 Like
After some confused fumbling around in trying to construct the key value pairs (it wasn’t actually that hard, I just forgot that I could execute code like normal, but in comptime), it works, and now I have only a single definition of each component in the interface.
pub const Component = union(enum) {
info: info,
attributes: attributes,
conditions: conditions,
unknown,
const impl_map = std.ComptimeStringMap(Component, blk: {
const tags = std.meta.tags(Component);
var res: [tags.len - 1]struct { []const u8, Component } = undefined;
for (tags, 0..) |tag, i| {
if (tag == .unknown) continue;
const name = @tagName(tag);
res[i] = .{ name, @unionInit(Component, name, .{}) };
}
const final = res;
break :blk final;
});
pub fn get(self: Component, id: u32, allocator: Allocator, state: State) []const u8 {
switch (self) {
inline else => |s| s.get(id, allocator, state),
}
}
pub fn put(self: Component, data: anytype) void {
switch (self) {
inline else => |s| s.put(data),
}
}
};
fn parse_component(path: []const u8) !Component {
var path_iter = std.mem.tokenizeAny(u8, path, "/");
_ = path_iter.next(); // dump the first item in the path
_ = path_iter.next(); // dump the id part of the path
if (path_iter.next()) |component_str| {
const component = Component.impl_map.get(component_str);
return component orelse .unknown;
}
return UrlError.ComponentTokenNotFound;
}
Thanks for helping me out.
1 Like
Happy to help!
Just be aware that, as of Zig 0.13.0, you’ll need to replace the ComptimeStringMap
with StaticStringMap(Component).initComptime(...)
. Same code, just refactored so it can be built at runtime as well.
Hey @Haxxflaxx, if the last code you posted solved your problem, please remember to mark it as a solution.