Labeled `switch` in Zig

Sep 10, 2024

Labeled switch in Zig

Zig's new labeled switch simplifies state transitions by allowing continue statements to target specific switch cases. This is especially useful for building state machines like tokenizers or parsers.

Simplifying state machines

In state machines, transitioning between states often involves loops or conditionals, which can be difficult to manage. With labeled switch, you can write more concise code.

state: switch (State.start) {
    .start => switch (self.buffer[self.index]) {
        'a'...'z' => {
            result.tag = .identifier;
            continue :state .identifier;
        },
        else => continue :state .invalid,
    },
    .identifier => {},
    .invalid => {},
}

Labeled switch lets you label the switch and use continue to jump to a specific case. You label the switch statement (state:) and use continue :state to jump directly to another case. This avoids the need for conditionals and loops.

The simplified example from the release notes shows the difference between traditional loops and labeled switch:

test "emulate labeled switch" {
    var op: u8 = 1;
    while (true) {
        switch (op) {
            1 => { op = 2; continue; },
            2 => { op = 3; continue; },
            3 => return,
            4 => {},
        }
        break;
    }
    return error.Unexpected;
}

While with labeled switch you can write:

test "labeled switch" {
    foo: switch (@as(u8, 1)) {
        1 => continue :foo 2,
        2 => continue :foo 3,
        3 => return,
        4 => {},
    }
    return error.Unexpected;
}

Performance

Labeled switch improves CPU branch prediction in hot loops like instruction dispatch or finite state machines. Traditional loops force the CPU to handle indirect branches, which can lead to inefficient branch prediction:

.LBB0_3:
    xor     edi, edi
    call    analyze_add
    jmp     .LBB0_15

The machine first jumps back to .LBB0_15, rechecks the loop condition, and then computes which case to execute next.

Labeled switch enables direct computed jumps between cases, reducing mispredictions.

.LBB0_3:
    mov     edi, 2
    call    analyze_addwrap
    mov     dword ptr [r14 + rbx], eax
    add     rbx, 4
    mov     eax, dword ptr [r15 + rbx]
    jmp     qword ptr [8*rax + .LJTI0_0] ; Computed jump based on state

In this version, instead of jumping back to a central point, each case computes and directly jumps to the next case in the state machine.

Further reading: