Composing Enums

I’m looking for an elegant way to compose enums that have repeat values. We can do this with error unions quite easily:

const A = error { a, b, c };
const B = error { x };
const C = error { y };

// we don't have to repeat a, b, c
const MyError1 = A || B;
const MyError2 = A || C;

But enums are quite a bit different here. There isn’t a global enum set we can coerce to.

Also, this does not work:

switch (mode) {
   A.x, A.y, A.z, B.x, B.y, B.z => // something
}

We can conditionally dispatch to sub-switch statements using comptime type checking via:

if (comptime @TypeOf(mode) == A) {
    return switch (mode) // do the A's
}
if (comptime @TypeOf(mode) == B) {
    return switch (mode) // do the B's
}

Any ideas?

This is very cursed and I do not recommend using it, but it’s the closest thing I could come up with.

const Enum1 = enum { a, b, c };
const Enum2 = enum { x };
const Enum3 = enum { y };

fn match(mode: anytype) i32 {
    switch (@TypeOf(mode)) {
        Enum1, Enum2, Enum3 => {},
        else => @compileError("expected known enum, found '" ++ @typeName(@TypeOf(mode)) ++ "'"),
    }
    return switch (mode) {
        inline else => |tag| switch (@as(anyerror, @field(anyerror, @tagName(tag)))) {
            error.a, error.b, error.x => 123,
            error.c, error.y => 456,
            else => 789,
        },
    };
}
2 Likes

Slightly less terrible alternative that uses regular if-statements instead of switches:

fn match(mode: anytype) i32 {
    if (matchProng(mode, .{ .a, .b, .x })) {
        return 123;
    } else if (matchProng(mode, .{ .c, .y })) {
        return 456;
    } else {
        return 789;
    }
}

fn matchProng(tag: anytype, enum_literals: anytype) bool {
    inline for (enum_literals) |enum_literal| {
        if (@hasField(@TypeOf(tag), @tagName(enum_literal)) and tag == enum_literal) return true;
    } else {
        return false;
    }
}
2 Likes

If the enums are plain, you could just keep the shared names in a tuple and the unique names in a tuple and then contruct a enum type from that using @Type.

But this doesn’t work if you want your enum to have methods or decls directly.
And the shared names are only related via their name.

2 Likes