📘 第04课:枚举与标签联合

📋 本课目录枚举类型 标签联合体 switch匹配 非穷举枚举 设计模式 练习

枚举类型

Zig的枚举是真正的类型安全枚举,不像C的枚举只是整数别名:

const std = @import("std");

// 基本枚举
const Direction = enum {
    north,
    south,
    east,
    west,
};

// 指定底层整数类型
const HttpStatus = enum(u16) {
    ok = 200,
    not_found = 404,
    server_error = 500,
};

// 枚举可以拥有方法
const Color = enum {
    red,
    green,
    blue,

    pub fn toRgb(self: Color) u24 {
        return switch (self) {
            .red => 0xFF0000,
            .green => 0x00FF00,
            .blue => 0x0000FF,
        };
    }

    pub fn name(self: Color) []const u8 {
        return switch (self) {
            .red => "red",
            .green => "green",
            .blue => "blue",
        };
    }
};

pub fn main() !void {
    const d = Direction.north;

    // 获取枚举的整数值
    const status = HttpStatus.ok;
    const code: u16 = @intFromEnum(status);  // 200

    // 从整数创建枚举(可能失败)
    const maybe_status = std.meta.intToEnum(HttpStatus, 404);
    if (maybe_status) |s| {
        std.debug.print("status: {}\n", .{s});
    } else {
        std.debug.print("invalid status\n", .{});
    }

    // 获取枚举字段名
    const name = @tagName(d);  // "north"

    // 枚举方法
    const c = Color.red;
    std.debug.print("{} rgb={x}\n", .{ c.name(), c.toRgb() });

    _ = code;
    _ = name;
}

标签联合体

标签联合体(tagged union)是Zig最强大的类型之一——一个值可以是多种类型之一,编译器确保安全访问:

const std = @import("std");

// 标签联合体:值只能是其中一种
const Value = union(enum) {
    int: i64,
    float: f64,
    string: []const u8,
    boolean: bool,
    null_val: void,

    // 标签联合体可以有方法
    pub fn format(self: Value, writer: anytype) !void {
        switch (self) {
            .int => |n| try writer.print("{}", .{n}),
            .float => |f| try writer.print("{d}", .{f}),
            .string => |s| try writer.print(\"{s}\"", .{s}),
            .boolean => |b| try writer.print("{}", .{b}),
            .null_val => try writer.writeAll("null"),
        }
    }

    // 安全的类型检查
    pub fn asInt(self: Value) ?i64 {
        return switch (self) {
            .int => |n| n,
            else => null,
        };
    }
};

// AST节点——标签联合体的经典应用
const Expr = union(enum) {
    number: f64,
    add: struct { left: *Expr, right: *Expr },
    multiply: struct { left: *Expr, right: *Expr },
    negate: *Expr,

    pub fn eval(self: *const Expr) f64 {
        return switch (self.*) {
            .number => |n| n,
            .add => |op| op.left.eval() + op.right.eval(),
            .multiply => |op| op.left.eval() * op.right.eval(),
            .negate => |expr| -expr.eval(),
        };
    }
};

pub fn main() !void {
    var values = [_]Value{
        .{ .int = 42 },
        .{ .float = 3.14 },
        .{ .string = "hello" },
        .{ .boolean = true },
        .null_val,
    };

    for (values) |v| {
        const stdout = std.io.getStdOut().writer();
        try v.format(stdout);
        try stdout.writeByte('\n');
    }

    // 访问错误字段会panic(Debug模式)
    // const n = values[0].float;  // panic! 是.int不是.float
}

switch匹配

switch是Zig中最强大的控制流——它能穷举枚举、解构联合体、匹配范围:

// switch解构标签联合体
fn describe(v: Value) []const u8 {
    return switch (v) {
        .int => |n| if (n > 0) "positive int" else "non-positive int",
        .float => "float",
        .string => |s| if (s.len > 10) "long string" else "short string",
        .boolean => "boolean",
        .null_val => "null",
    };
}

// switch + 指针修改
fn increment(v: *Value) void {
    switch (v.*) {
        .int => |*n| n.* += 1,
        .float => |*f| f.* += 1.0,
        else => {},
    }
}

// switch必须是穷举的!
// 如果Value有5个变体,你必须处理全部5个
// 可以用 else => {} 兜底

非穷举枚举

当与C互操作时,枚举值可能不完整——使用非穷举枚举:

// 非穷举枚举:允许未知值
const ElfSectionType = enum(u32) {
    null = 0,
    progbits = 1,
    symtab = 2,
    strtab = 3,
    rela = 4,
    // _ 占位符捕获所有未列出的值
    _,

    pub fn name(self: ElfSectionType) []const u8 {
        return switch (self) {
            .null => "NULL",
            .progbits => "PROGBITS",
            .symtab => "SYMTAB",
            .strtab => "STRTAB",
            .rela => "RELA",
            _ => "UNKNOWN",  // 未知值
        };
    }
};

// 从任意整数安全创建
const unknown: ElfSectionType = @enumFromInt(99);
std.debug.print("{}\n", .{unknown.name()});  // "UNKNOWN"

设计模式

状态机

const State = enum {
    idle,
    connecting,
    connected,
    closing,
};

const Event = union(enum) {
    connect,
    data: []const u8,
    disconnect,
    timeout,
};

fn transition(current: State, event: Event) State {
    return switch (current) {
        .idle => switch (event) {
            .connect => .connecting,
            else => .idle,
        },
        .connecting => switch (event) {
            .data => .connected,
            .timeout => .idle,
            .disconnect => .idle,
            .connect => .connecting,
        },
        .connected => switch (event) {
            .disconnect => .closing,
            .data => .connected,  // 继续接收数据
            .timeout => .connected,
            .connect => .connected,
        },
        .closing => .idle,
    };
}

练习

📝 课后练习

  1. 日志级别:创建LogLevel枚举(debug/info/warn/error),实现shouldLog(level, config)
  2. JSON值:用标签联合体实现JsonValue,支持null/bool/int/float/string/array/object
  3. 计算器AST:实现四则运算的AST,用标签联合体,编写eval求值
  4. 状态机:实现TCP连接状态机(CLOSED/SYN_SENT/ESTABLISHED/FIN_WAIT等)
  5. 非穷举枚举:为Linux syscall号实现非穷举枚举,只列出常用的10个