Confused about @tagName and comptime behavior

I’m working on a library for a project targeting Arm Cortex-M. I want to convert a enum value to an integer based on the enum tag name.

For example:

const E = enum {
    mul4,
    mul8,
    mul16,
};

// This should map:
// mul4  -> 4
// mul8  -> 8
// mul16 -> 16
fn mulVal(x: anytype) u32 { ... }

The implementation that ended up working is:

pub fn mulDivValue(mul_or_div: anytype) u32 {
    switch (mul_or_div) {
        inline else => |tag| {
            @setEvalBranchQuota(50000);

            // Comptime checks to make sure variant name is translated correctly
            const tagName = @tagName(tag);
            if (comptime !(std.mem.startsWith(u8, tagName, "div") or std.mem.startsWith(u8, tagName, "mul"))) {
                @compileError("enum variant must start with 'div' or 'mul': " ++ tagName);
            }
            const tagDigits = tagName[3..];
            inline for (tagDigits) |c| {
                if (comptime !std.ascii.isDigit(c)) @compileError("enum variant must contain only digits after prefix: " ++ tagName);
            }

            const val = comptime std.fmt.parseUnsigned(u32, tagDigits, 10) catch unreachable;
            return val;
        },
    }
}

With const val = comptime ... it works but removing comptime ends up derefencing an invalid address.

My guess is something related to @tagName but I don’t really understand how it works. I thought I wouldn’t need to force it to comptime.

Why do I need to explicitly mention comptime in val assignment?

Would be better to show the actual error message.

Why aren’t you using?:

const E = enum {
    mul4 = 4,
    mul8 = 8,
    mul16 = 16,
};

That way you can use @intFromEnum to get the value.

Or possibly even:

const E = enum {
    @"4" = 4,
    @"8" = 8,
    @"16" = 16,
};
const MulDiv = union(enum) {
    mul: E,
    div: E,

    pub fn mul(v:u8) MulDiv {
        return .{ .mul = @enumFromInt(v) };
    }
    pub fn div(v:u8) MulDiv {
        return .{ .div = @enumFromInt(v) };
    }
};

// const md:MulDiv = .mul(4);
4 Likes

Removing comptime from the integer parse shouldn’t result in a compiler error. I just tested on 0.16 and nightly master and it works fine. What version of Zig are you using?

However, you should use comptime in that statement (or better yet, put the whole extraction of the numerical value inside of a comptime block). The reason is that functions don’t run in comptime automatically, even if all of the arguments are comptime known. So parseUnsigned would be run every time the function was called.

but generally yeah this usage looks like a bit of an overuse of comptime without further context. Never underestimate the power of just writing out a few values.

The reason it’s not like that is because the code for the enums is generated from some chip description files and the enum values correspond to how bits need to be set in order to configure the hardware. I’m just trying to not code the translation by hand. I’ll eventually move to something along the lines of what you suggested. I really like the last one.

So there are a lot of variants, that’s why I resorted to comptime.

I wasn’t aware of that! Thanks for the tip :slight_smile:.

It’s not a compiler error. The error happens at runtime. I’m still debugging the issue but I just confirmed that it’s not related to comptime vs no comptime code generation.

A bit of context: I’m using zig 0.17.0-dev and I’m working on an implementation of std.Io to use in some of my embedded systems projects. That requires me to setup the stacks for each “thread” and, from what I could see debugging this issue, without making the numerical value computation comptime the compiler seems to generate a big jump table that it doesn’t generate otherwise and that seems to cause a stack overflow for some reason (just checked it works if I increase the thread stack size).

I just confirmed this was a stack overrun caused by not having enough space to execute the parseUnsigned at runtime. It required ~2.5kb of stack space (vs. ~60 bytes when parsing is done at comptime), which is more than what I had for each thread.

This ended up not being completely related to what I asked but thanks for the useful suggestions and tips @Sze and @ScottRedig.