Why can't I use a function call as a switch prong in Zig?

Question: Why can’t I use a function call as a switch prong in Zig, but direct range checks work?

Hi everyone,

I’m running into an issue with a switch statement in my Zig code, and I’m hoping someone can explain what’s going on
I have a simple function to check if a byte is alphabetic same when using std.ascii.isAlphabetic

pub fn isAlphabetic(c: u8) bool {
    return switch (c) {
        'A'...'Z', 'a'...'z' => true,
        else => false,
    };
}

Then, in another part of my code, I’m switching on a character chr like this:

const chr: u8 = self.buffer[self.idx];
switch (chr) {
    // same for std.ascii.isAlphabetic
    isAlphabetic(chr) => |ascii| {
        _ = ascii;
    },
    '0'...'9' => {
        return self.numberToken();
    },
    // other cases...
}

This gives me the following compile error:

src/main.zig:52:26: error: unable to resolve comptime value
            isAlphabetic(chr) => |ascii| {
                         ^~~
src/main.zig:52:25: note: switch prong values must be comptime-known
            isAlphabetic(chr) => |ascii| {

However, if I inline the range checks directly in the switch instead of using the function:

switch (chr) {
    'A'...'Z', 'a'...'z' => |ascii| {
        _ = ascii;
    },
    '0'...'9' => {
        return self.numberToken();
    },
    // other cases...
}

It compiles just fine.

Why is this happening? Is it because switch prongs need to be comptime-known values, and calling a function like isAlphabetic(chr) isn’t considered comptime even though the function itself uses comptime ranges? Does the documentation mention this requirement for switch statements? I’d love to understand the reasoning behind why the function call doesn’t work but the direct usage does.

Thanks in advance for any insights!

There’s several issues: first, yeah, isAlphabetic() can’t be comptime because it has a runtime paramter, even though it ‘statically evaluates’ this runtime parameter. The other issue though is that your isAlphabetic() function returns a boolean, while the switch works on u8 values.

A switch is not a replacement for a dynamic if-else cascade, think of it more like a static jump table which you populate at comptime via the prong-values - and a function that returns a boolean doesn’t really work with the jump table idea even if it can be evaluated at comptime, it would need to return a “jump table index” instead of a true/false value - and I think in that case you’d also need to use an ‘inline prong’ so that Zig ‘unrolls’ the comptime function call into all resulting prongs…

PS: …one interesting question would be if a non-comptime inline function should be accepted in a switch-prong… since inline in Zig hoists the function body into the caller - e.g. more like a C macro, and the compiler should then be able to figure out if the hoisted code can be evaluated at comptime even without being explicitely marked as comptime…

8 Likes

I wonder if it wouldn’t actually make sense to allow the use of arrays as switch prong. Something like this:

fn isVowel(letter: u8) bool {
    return switch (letter) {
        vowels() => true,
        else => false,
    };
}

fn vowels() [5]u8 {
    return .{ 'a', 'e', 'i', 'o', 'u' };
}

I would expect that to be for switching on arrays/slices, not as an equivalent to multiple cases for a branch

I am inclined to agree with this.

2 Likes

I think the improvement to readability justifies having such a feature. We can today handle the use cases with inline for, but the semantic is poor. Using a fake loop to emulate a switch is just not a nice way of doing things.

1 Like

Safe bet that for the more complex cases, emulating it with inline for simply won’t give the compiler what it needs to generate efficient code. There’s been some movement on that issue, so here’s hoping.

1 Like