Rust 枚举与模式匹配

如果说所有权是 Rust 的灵魂,那枚举 + match 就是 Rust 最优雅的日常工具。Rust 的枚举远比 C/Java 强大——每个变体可以携带不同类型的数据,配合 match 模式匹配,处理分支逻辑极为简洁安全。这一篇是 Rust 实战的核心,务必掌握。

1. Rust 枚举:不只是常量

在 C/Java 里,枚举本质是"有名字的整数"。Rust 的枚举完全不同——它是代数数据类型(ADT),每个变体可以携带不同类型的数据:

// Rust 的枚举比 C/Java 强大得多
// 每个变体(variant)可以【携带不同类型的数据】
// 这叫"代数数据类型"(ADT)

enum Message {
    Quit,                          // 无数据
    Move { x: i32, y: i32 },       // 像结构体(命名字段)
    Write(String),                 // 元组形式(一个 String)
    ChangeColor(i32, i32, i32),    // 元组形式(三个 i32)
}

fn main() {
    // 创建枚举值:用 :: 访问变体
    let m1 = Message::Quit;
    let m2 = Message::Move { x: 10, y: 20 };
    let m3 = Message::Write("hello".to_string());
    let m4 = Message::ChangeColor(255, 0, 0);

    process(m1);
    process(m3);
}

// 用 match 处理每种变体(必须穷尽!)
fn process(msg: Message) {
    match msg {
        Message::Quit => println!("退出"),
        Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
        Message::Write(text) => println!("写入: {}", text),
        Message::ChangeColor(r, g, b) => println!("颜色 RGB({},{},{})", r, g, b),
    }
}

这个能力的威力在于:你可以用一个 enum 表达"几种互斥的情况",每种情况有自己的数据结构。比如聊天协议的消息、AST 节点、状态机状态,都用 enum + match 清晰建模。

2. Option:Rust 没有 null

Rust 没有 null(空指针)——这是它的核心设计决定。"Null 引用是十亿美元错误"(Tony Hoare 名言),无数 bug 源于"忘记检查 null"。Rust 用 Option<T> 枚举替代:

// Option<T>:Rust 处理"可能没有值"的方式
// 替代了其他语言的 null / None / nil

enum Option<T> {
    Some(T),      // 有值,类型 T
    None,         // 没值
}

fn main() {
    let some_number: Option<i32> = Some(42);
    let no_number: Option<i32> = None;

    // 必须显式处理"没有值"的情况,编译器强制
    match some_number {
        Some(n) => println!("有值: {}", n),
        None => println!("没值"),
    }

    // 常见用法:从某些操作获取 Option
    let arr = [1, 2, 3];
    let first = arr.get(0);       // Option<&i32> = Some(&1)
    let out = arr.get(10);        // Option<&i32> = None(不 panic!)

    // .get 比 [index] 安全:越界返回 None 而不是 panic
    if let Some(v) = out {
        println!("拿到: {}", v);
    } else {
        println!("索引越界");
    }
}

// 为什么 Option 比 null 好?
// 1. 类型系统强迫你处理 None 的情况,不会"忘了判空"
// 2. Option<T> 和 T 是不同的类型——你不能直接用 Some(5) 当 i32
//    必须先 unwrap 或 match 出来,这就保证了安全
// "Null 引用是十亿美元错误" —— Tony Hoare

Option 的设计哲学:

3. match 模式匹配

match 是处理枚举(以及各种值)的标准方式。它强大在模式(Pattern)的丰富性:

fn main() {
    let num = 3;

    // ===== match 的各种模式 =====
    match num {
        // 字面量模式
        1 => println!("一"),
        // 或模式(多个值匹配同一分支)
        2 | 3 | 4 => println!("二三四"),
        // 范围模式(闭区间)
        5..=9 => println!("五到九"),
        // 通配符 _ 匹配剩余所有(必须放最后)
        _ => println!("其他"),
    }

    // match 是表达式,有返回值
    let text = match num {
        1 => "one",
        2 => "two",
        _ => "many",
    };
    println!("{}", text);

    // 各分支类型必须一致
    // match num {
    //     1 => "str",
    //     _ => 42,        // 编译错误!类型不匹配
    // }
}

几个核心特性:

4. 解构(强大模式)

match 最强大的能力是解构(destructure)——把元组、结构体、枚举的内部数据"拆"出来:

fn main() {
    // ===== 解构元组 =====
    let point = (3, 5);
    match point {
        (0, 0) => println!("原点"),
        (0, y) => println!("y 轴,y = {}", y),
        (x, 0) => println!("x 轴,x = {}", x),
        (x, y) => println!("普通点 ({}, {})", x, y),
    }

    // ===== 解构结构体 =====
    struct Point { x: i32, y: i32 }
    let p = Point { x: 5, y: -3 };
    match p {
        Point { x: 0, y } => println!("在 y 轴, y = {}", y),
        Point { x, y: 0 } => println!("在 x 轴, x = {}", x),
        // 简写:字段名和变量名相同时
        Point { x, y } => println!("({}, {})", x, y),
    }

    // ===== 解构枚举(配合变体数据)=====
    enum Shape {
        Circle(f64),
        Rectangle(f64, f64),
    }
    let s = Shape::Rectangle(3.0, 4.0);
    let area = match s {
        Shape::Circle(r) => 3.14 * r * r,
        Shape::Rectangle(w, h) => w * h,
    };
    println!("面积: {}", area);
}

解构让代码非常简洁——一步完成"判断 + 取值"。配合枚举,处理分支逻辑极为优雅。这是函数式编程的经典技巧,Rust 把它做到了极致。

5. if let / while let:简写

当你只关心一种情况时,用 if let 比 match 更简洁。它是 match 的语法糖:

fn main() {
    let some: Option<i32> = Some(42);

    // 当只关心一种情况时,用 if let 比 match 更简洁
    // match 写法(啰嗦):
    match some {
        Some(x) => println!("拿到: {}", x),
        _ => (),                  // 必须处理其他情况
    }

    // if let 写法(简洁):只关心 Some
    if let Some(x) = some {
        println!("拿到: {}", x);
    } else {
        println!("没值");           // else 可选
    }

    // while let:循环匹配,直到不匹配
    let mut stack = vec![1, 2, 3];
    while let Some(top) = stack.pop() {
        println!("弹出: {}", top);   // 3, 2, 1
    }

    // 经验:
    // - 关心两种以上情况 -> 用 match
    // - 只关心一种情况,其他忽略 -> 用 if let
}

经验法则:

6. 枚举也能有方法

和结构体一样,枚举也能有 impl 块,定义方法和关联函数:

// 枚举也能有 impl 块(像结构体一样)
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    fn duration(&self) -> u32 {
        match self {
            TrafficLight::Red => 30,
            TrafficLight::Yellow => 5,
            TrafficLight::Green => 45,
        }
    }

    fn next(&self) -> TrafficLight {
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Green => TrafficLight::Yellow,
            TrafficLight::Yellow => TrafficLight::Red,
        }
    }
}

fn main() {
    let light = TrafficLight::Red;
    println!("红灯持续 {} 秒", light.duration());
    let next = light.next();
    println!("下一个是绿灯,持续 {} 秒", next.duration());
}

枚举 + impl + match 是 Rust 处理"状态机"的标配。比如红绿灯、订单状态、解析器状态等,都用这种方式建模,代码既清晰又安全(不会进入未定义状态)。

7. 模式匹配的更多技巧

match 的模式还有几个常用技巧:

这些进阶技巧在标准库和第三方库里随处可见,熟练掌握后,你能写出非常地道的 Rust 代码。

8. 枚举 vs 结构体:何时用哪个?

选择指南:

经验:当你发现自己在写"如果 A 类型,这样处理;如果 B 类型,那样处理"时,就该用枚举了。枚举让"可能性"显式化,match 强制处理每种情况。

9. 实战:解析命令

用枚举 + match 建模"用户输入的命令",是 Rust 的经典模式:

小结

这一篇你掌握了 Rust 枚举和模式匹配的威力:枚举(变体可携带数据,ADT)、Option<T>(替代 null,强制处理空值)、match(穷尽模式匹配)、解构(拆出内部数据)、if let / while let(简写)。枚举 + match 是 Rust 处理分支逻辑的标配,比 switch + if-null 安全优雅太多。

下一篇我们看 Trait(特征)——Rust 实现抽象和多态的方式,它和"接口"概念相似但有独特之处。

← 上一篇 Rust 结构体

下一篇 Rust Trait 特征

✈️💬