Comptime Enum Value Declarations

I’m pretty sure I already know the answer, but wanted to throw this out there as a last ditch. Let’s say I have an enum that can have different fields depending on some compile time known configuration. A working example of how I could do this is as follows:

const std = @import("std");

const comptime_option = true;

const SomeEnum = if (comptime_option) enum(u32) {
    a = 0,
    b,
    c,
    d,

    pub fn someMethod(self: SomeEnum) u32 {
        return @intFromEnum(self);
    }
} else enum(u32) {
    // Different ints for each value!
    a = 0,
    c,
    d,
    b,
    // More values!
    x,
    z,

    pub fn someMethod(self: SomeEnum) u32 {
        return @intFromEnum(self);
    }
};

pub fn main() void {
    std.debug.print("Works: {d}\n", .{SomeEnum.a.someMethod()});
    std.debug.print("Compile Error!: {d}\n", .{SomeEnum.x.someMethod()});
}

This accomplishes what I’m after, however you’ll notice the repeated code in both arms of the enum definition:

    pub fn someMethod(self: SomeEnum) u32 {
        return @intFromEnum(self);
    }

Any way to avoid this repeated method definition or is this sorta just the way it is?

I’ll wait for other answers to roll in, but this post from @AndrewCodeDev coincidentally sort of answered my question! Here is a decent way to avoid code duplication when you have many methods that might be rather long:

const std = @import("std");

const comptime_option = true;

const SomeEnumImpl = struct {
    pub fn someMethod(self: SomeEnum) u32 {
        return @intFromEnum(self);
    }
    pub fn otherMethod(self: SomeEnum) u32 {
        return @intFromEnum(self) * 2;
    }
};

const SomeEnum = if (comptime_option) enum(u32) {
    a = 0,
    b,
    c,
    d,

    pub const someMethod = SomeEnumImpl.someMethod;
    pub const otherMethod = SomeEnumImpl.otherMethod;
} else enum(u32) {
    // Different ints for each value!
    a = 0,
    c,
    d,
    b,
    // More values!
    x,
    z,

    pub const someMethod = SomeEnumImpl.someMethod;
    pub const otherMethod = SomeEnumImpl.otherMethod;
};

pub fn main() void {
    std.debug.print("Works: {d}\n", .{SomeEnum.b.someMethod()});
    std.debug.print("Compile Error!: {d}\n", .{SomeEnum.x.someMethod()});
}

You duplicate code by defining the public const members for each method, but is a lot more compact than duplicating the entire method code.

2 Likes

At the moment we don’t have a way to attach decls to a type created though @Type(), so the best you can do is avoiding redeclaration of methods using Andrew’s method.

2 Likes

The eloquent way to get this is usingnamespace.

fn EnumImpls(E: type) type {
    return struct {
         pub fn methodA(self: E, ...) void {
              // ... 
         }
         // etc
    };
}

const SomeEnum = if (comptime_thing) enum(u32) {
   fee,
   fie, 
   foe,

   pub usingnamespace EnumImpls(@This());
} else enum(u32) {
    other,
    tags,
    here,
     
    pub usingnamespace EnumImpls(@This());
};

Single source of truth.

1 Like

The more I look into it, the more I am torn on usingnamespace - have we opened a thread dedicated to this topic yet? I think we should if we haven’t.

1 Like

I don’t think so, and that might be a good idea. The issue on the tracker has seen a lot of action, a discussion here would end up fairly high signal, that’s likely to be a net positive contribution.

I’ll encourage you to start a brainstorm when you find time, you would do a good job of being evenhanded about the pros and cons. I’m decidedly pro, wouldn’t want to put my thumb on the scale as the first post on the topic.

1 Like