Labeled Switch

The switch statement in Zig has an unusual feature: if we add a label, we can use continue to jump back to the start of the switch. It becomes a loop and a switch in one.

The following example is a very simple virtual machine. It runs a sequence of instructions that modify a number and prints the result:

const std = @import("std");
const print = std.debug.print;

const Instruction = enum {
    increment, // increment the number
    decrement, // decrement the number
    skip, // skip the next instruction
    halt, // stop the virtual machine
};

pub fn main() void {
    // the instructions to be interpreted
    const instructions = [_:.halt]Instruction{
        .increment,
        .skip,
        .decrement,
        .increment,
    };

    // initial state
    var state: i64 = 0;

    // process the instructions
    var index: u64 = 0;
    sw: switch (instructions[index]) {
        .increment => {
            state += 1;
            index += 1;
            continue :sw instructions[index];
        },
        .decrement => {
            state -= 1;
            index += 1;
            continue :sw instructions[index];
        },
        .skip => {
            index += 2;
            continue :sw instructions[index];
        },
        .halt => {},
    }

    // print the result
    print("Result: {}\n", .{state});
}

We’re using a sentinel-terminated array to make sure the last instruction is always halt.

$ zig run labeled-switch.zig 
Result: 2

Switch expressions

The next example uses switch as an expression:

const std = @import("std");
const print = std.debug.print;
const expect = std.testing.expect;

const State = enum {
    start,
    first_digit,
    more_digits,
};

// parseNumber returns true if the input can be parsed.
fn parseNumber(input: [*:0]const u8) bool {
    var index: u64 = 0;
    return sw: switch (State.start) {
        .start => {
            switch (input[index]) {
                '+', '-' => {
                    index += 1;
                    continue :sw .first_digit;
                },
                '0'...'9' => {
                    continue :sw .first_digit;
                },
                else => {
                    // Use 'break' to yield a value:
                    break :sw false;
                },
            }
        },
        .first_digit => {
            switch (input[index]) {
                '0'...'9' => {
                    index += 1;
                    continue :sw .more_digits;
                },
                else => {
                    break :sw false;
                },
            }
        },
        .more_digits => {
            switch (input[index]) {
                0 => {
                    break :sw true;
                },
                '0'...'9' => {
                    index += 1;
                    continue :sw .more_digits;
                },
                else => {
                    break :sw false;
                },
            }
        },
    };
}

test "parseNumber returns true for valid integer" {
    try expect(parseNumber("123"));
    try expect(parseNumber("+123"));
    try expect(parseNumber("-123"));
}

test "parseNumber returns false for invalid integer" {
    try expect(!parseNumber(""));
    try expect(!parseNumber("+"));
    try expect(!parseNumber("abc"));
    try expect(!parseNumber("-x"));
    try expect(!parseNumber("1y"));
}
$ zig test labeled-switch-2.zig 
All 2 tests passed.

This is the last example for now.