Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content
zig 版本:0.14.0

Switch

switch 语句可以进行匹配,并且 switch 匹配不能出现遗漏匹配的情况。

基本使用

zig
const num: u8 = 5;
switch (num) {
    5 => {
        print("this is 5\n", .{});
    },
    else => {
        print("this is not 5\n", .{});
    },
}
zig
const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    const num: u8 = 5;
    switch (num) {
        5 => {
            print("this is 5\n", .{});
        },
        else => {
            print("this is not 5\n", .{});
        },
    }
}

🅿️ 提示

switch 的匹配必须要穷尽所有,或者具有 else 分支!

进阶使用

switch 还支持用 , 分割的多匹配、... 的范围选择符,类似循环中的 tag 语法、编译期表达式,以下是演示:

zig
const a: u64 = 10;
const zz: u64 = 103;

// 作为表达式使用
const b = switch (a) {
    // 多匹配项
    1, 2, 3 => 0,

    // 范围匹配
    5...100 => 1,

    // tag形式的分配匹配,可以任意复杂
    101 => blk: {
        const c: u64 = 5;
        // 下一行代表返回到blk这个tag处
        break :blk c * 2 + 1;
    },

    zz => zz,
    // 支持编译期运算
    blk: {
        const d: u32 = 5;
        const e: u32 = 100;
        break :blk d + e;
    } => 107,

    // else 匹配剩余的分支
    else => 9,
};

try expect(b == 1);

作为表达式使用

在 zig 中,还可以将 switch 作为表达式来使用:

zig
const os_msg = switch (builtin.target.os.tag) {
    .linux => "we found a linux user",
    else => "not a linux user",
};
zig
const builtin = @import("builtin");

pub fn main() void {
    const os_msg = switch (builtin.target.os.tag) {
        .linux => "we found a linux user",
        else => "not a linux user",
    };
    _ = os_msg;
}

捕获 Tag Union

我们还可以使用 switch 对标记联合类型进行捕获操作,对字段值的修改可以通过在捕获变量名称之前放置 * 并将其转换为指针来完成:

zig
// 定义两个结构体
const Point = struct {
    x: u8,
    y: u8,
};
const Item = union(enum) {
    a: u32,
    c: Point,
    d,
    e: u32,
};

var a = Item{ .c = Point{ .x = 1, .y = 2 } };

const b = switch (a) {
    // 多个匹配
    Item.a, Item.e => |item| item,

    // 可以使用 * 语法来捕获对应的指针进行修改操作
    Item.c => |*item| blk: {
        item.*.x += 1;
        break :blk 6;
    },

    // 这里最后一个联合类型,匹配已经穷尽了,我们就不需要使用else了
    Item.d => 8,
};

std.debug.print("{any}\n", .{b});

匹配和推断枚举

在使用 switch 匹配时,也可以继续对枚举类型进行自动推断:

zig
const Color = enum {
    auto,
    off,
    on,
};
const color = Color.off;
// 编译器会帮我们完成其余的工作
const result = switch (color) {
    .auto => false,
    .on => false,
    .off => true,
};

内联 switch

switch 的分支可以标记为 inline 来要求编译器生成该分支对应的所有可能分支:

zig
// 这段函数用来判断一个结构体的字段是否是 optional,同时它也是 comptime 的
// 故我们可以在下面使用inline 来要求编译器帮我们展开这个switch
fn isFieldOptional(comptime T: type, field_index: usize) !bool {
    const fields = @typeInfo(T).Struct.fields;
    return switch (field_index) {
        // 这里每次都是不同的值
        inline 0...fields.len - 1 => |idx| {
            return @typeInfo(fields[idx].type) == .Optional;
        },
        else => return error.IndexOutOfBounds,
    };
}

inline else 可以展开所有的 else 分支,这样做的好处是,允许编译器在编译时显式生成所有分支,这样在编译时可以检查分支是否均能被正确地处理:

zig
const AnySlice = union(enum) {
    a: u8,
    b: i8,
    c: bool,
    d: []u8,
};

fn withSwitch(any: AnySlice) usize {
    return switch (any) {
        // 这里的 slice 可以匹配所有的 Anyslice 类型
        inline else => |slice| _ = slice,
    };
}

当使用 inline else 捕获 tag union 时,可以额外捕获 tag 和对应的 value:

zig
const U = union(enum) {
    a: u32,
    b: f32,
};

fn getNum(u: U) u32 {
    switch (u) {
        // 这里 num 是一个运行时可知的值
        // 而 tag 则是对应的标签名,这是编译期可知的
        inline else => |num, tag| {
            if (tag == .b) {
                return @intFromFloat(num);
            }
            return num;
        },
    }
}

labeled switch

这是 0.14.0 引入的新特性,当 switch 语句带有标签时,它可以被 breakcontinue 语句引用。break 将从 switch 语句返回一个值。

针对 switchcontinue 语句必须带有一个操作数。当执行时,它会跳转到匹配的分支,就像用 continue 的操作数替换了初始 switch 值后重新执行 switch 一样。

例如以下两段代码的写法是一样的:

zig
sw: switch (@as(i32, 5)) {
    5 => continue :sw 4,

    // `continue` can occur multiple times within a single switch prong.
    2...4 => |v| {
        if (v > 3) {
            continue :sw 2;
        } else if (v == 3) {

            // `break` can target labeled loops.
            break :sw;
        }

        continue :sw 1;
    },

    1 => return,

    else => unreachable,
}
zig
var sw: i32 = 5;
while (true) {
    switch (sw) {
        5 => {
            sw = 4;
            continue;
        },
        2...4 => |v| {
            if (v > 3) {
                sw = 2;
                continue;
            } else if (v == 3) {
                break;
            }

            sw = 1;
            continue;
        },
        1 => return,
        else => unreachable,
    }
}

这可以提高(例如)状态机的清晰度,其中 continue :sw .next_state 这样的语法是明确的、清楚的,并且可以立即理解。

不过,这个设计的目的是处理对数组中每个元素进行 switch 判断的情况,在这种情况下,使用单个 switch 语句可以提高代码的清晰度和性能:

zig
const Instruction = enum {
    add,
    mul,
    end,
};

fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {
    const std = @import("std");
    var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);
    var ip: usize = 0;

    return vm: switch (code[ip]) {
        // Because all code after `continue` is unreachable, this branch does
        // not provide a result.
        .add => {
            try stack.append(stack.pop().? + stack.pop().?);

            ip += 1;
            continue :vm code[ip];
        },
        .mul => {
            try stack.append(stack.pop().? * stack.pop().?);

            ip += 1;
            continue :vm code[ip];
        },
        .end => stack.pop().?,
    };
}

如果 continue 的操作数在编译时就已知,那么它可以被简化为一个无条件分支指令,直接跳转到相关的 case。这种分支是完全可预测的,因此通常执行速度很快。

如果操作数在运行时才能确定,每个 continue 可以内联嵌入一个条件分支(理想情况下通过跳转表实现),这使得 CPU 可以独立于其他分支来预测其目标。相比之下,基于循环的简化实现会迫使所有分支都通过同一个分发点,这会妨碍分支预测。