Tagged union with comptime tag

I am not quite sure whether this is what you want.

What you write sounds like you want to generate separate functions at compile time.
To me that seems like overkill, I think using the log function in my example below, with the tag would satisfy me.

If you really want to use separate functions, I think you would need to generate source code at build time.
(I am not completely sure on that, but it seems to me, that you can’t easily generate completely new function declarations with comptime)

Here is a suggestion, that just uses the tag as a comptime parameter:

const std = @import("std");

const Value = []const u8;
const Change = union(enum) {
    const Self = @This();
    const Tag = std.meta.Tag(Self);

    Insert: struct { index: u32, value: Value },
    Update: struct { index: u32, value: Value },
    Delete: struct { index: u32 },

    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = fmt;
        _ = options;
        switch (self) {
            .Insert => |s| try writer.print("Insert {d} {s}", .{ s.index, s.value }),
            .Update => |s| try writer.print("Update {d} {s}", .{ s.index, s.value }),
            .Delete => |s| try writer.print("Delete {d}", .{s.index}),
        }
    }
};

const ChangeLog = struct {
    const Self = @This();

    const Changes = std.ArrayList(Change);
    changes: Changes,

    pub fn init(allocator: std.mem.Allocator) Self {
        return .{ .changes = Changes.init(allocator) };
    }
    pub fn deinit(self: *Self) void {
        self.changes.deinit();
    }

    pub fn logChange(self: *Self, change: Change) !void {
        try self.changes.append(change);
    }
    pub fn log(self: *Self, comptime tag: Change.Tag, payload: std.meta.TagPayload(Change, tag)) !void {
        try self.logChange(@unionInit(Change, @tagName(tag), payload));
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer {
        switch (gpa.deinit()) {
            .leak => @panic("leaked memory"),
            else => {},
        }
    }

    var changes = ChangeLog.init(allocator);
    defer changes.deinit();
    try changes.log(.Insert, .{ .index = 4, .value = "foo" });
    try changes.log(.Update, .{ .index = 4, .value = "bar" });
    try changes.log(.Delete, .{ .index = 4 });

    for (changes.changes.items) |c| {
        std.debug.print("{}\n", .{c});
    }
}
3 Likes