Rust: Rust 枚举(Enum)与 Option:模式匹配的完美搭档

最后更新:2026-08-26

枚举(Enum)是 Rust 最强大的类型之一——它让一个值可以是一组可能变体中的某一个,且每个变体可以携带不同的数据。

如果说结构体是"和"类型(同时拥有所有字段),那枚举就是"或"类型(要么是这个,要么是那个)。


1. 你将学到


2. 概念图解

100%
flowchart LR
    subgraph "enum Option<T>"
        S["Some(T)<br>有值"]
        N["None<br>无值"]
    end
    subgraph "使用场景"
        V["计算结果"] -->|"成功"| S
        F["计算结果"] -->|"失败/空"| N
    end
    S --> MATCH["match 处理"]
    N --> MATCH
    MATCH -->|"Some(v) => 使用值 v"| OK["✅ 安全"]
    MATCH -->|"None => 处理空值"| SAFE["✅ 无崩溃"]

3. 一个订单系统的故事

(1) 痛苦:用数字表示订单状态

Tom 在开发电商系统,订单有 4 种状态:

TEXT 📖 仅展示
0 = 待支付, 1 = 已支付, 2 = 已发货, 3 = 已完成
RUST
let order_status = 0;  // 待支付
// 但万一有人写出 order_status = 99 呢?
// 或者把状态和品数量搞混:let order_status = product_count;

用整数表示状态是 C 语言的老套路——它不 type-safe。任何整数都可以赋值给"状态",编译器不会帮你检查。而且状态之间没有关联,易出错。

(2) Rust 枚举的方案

RUST
enum OrderStatus {
    Pending,       // 待支付
    Paid,          // 已支付
    Shipped,       // 已发货——可以携带快递单号
    Delivered,     // 已完成——可以携带送达时间
}

fn main() {
    let status = OrderStatus::Paid;

    match status {
        OrderStatus::Pending => println!("请完成支付"),
        OrderStatus::Paid => println!("已支付,等待发货"),
        OrderStatus::Shipped => println!("已发货,在路上"),
        OrderStatus::Delivered => println!("已送达,感谢购买"),
    }

    // 编译器确保:你不会忘记处理任何状态!
    // 如果新增了状态 Cancelled,忘记更新 match,编译器会报错
}

枚举把所有可能的"选择"集中定义在一个类型里,match 配合枚举确保了穷尽性检查——每个状态都被处理,不会遗漏。


4. 枚举类型

(1) 枚举变体可以携带数据

RUST
enum Message {
    Quit,                       // 没有数据
    Move { x: i32, y: i32 },   // 匿名结构体
    Write(String),              // 单个值
    ChangeColor(i32, i32, i32), // 元组
}
枚举变体 携带的数据 使用方式
Quit 只是一个标记
Move { x, y } 匿名结构体 Message::Move { x: 10, y: 20 }
Write(String) 元组结构体 Message::Write("hello".to_string())
ChangeColor(i,i,i) 元组结构体 Message::ChangeColor(255, 0, 0)

(2) Option 常用方法速查

方法 返回类型 说明 None 时行为
unwrap() T 提取值 panic
expect(msg) T 提取值(自定义消息) panic + msg
unwrap_or(default) T 提取值或默认值 返回 default
map(f) Option<U> 转换 Some 中的值 保持 None
and_then(f) Option<U> 链式 Option 操作 保持 None
filter(f) Option<T> 条件过滤 不满足则 None
is_some() bool 是否有值 false
is_none() bool 是否无值 true
ok_or(err) Result<T, E> 转为 Result Err(err)

(3) Option vs null 对比

维度 Option<T> null(其他语言)
类型安全 编译器强制处理空值情况 任何引用都可能为 null
空值检查 match/方法链自动覆盖 需要手动 if 检查
忘记检查 编译错误 运行时 NullPointerException
链式操作 map/and_then 等 需要 if 嵌套或可选链

(4) Enum 的内存布局

枚举占用的内存 = 最大变体占用 + 1 字节的标签(tag)。编译器根据 tag 知道当前存储的是哪个变体。

100%
graph TB
    subgraph "Message 枚举在内存中"
        TAG[tag: 1 字节] -->|标识当前是哪个变体| LABEL
        DATA[最大变体的数据: 8 字节] --> LABEL[一共 9 字节]
    end

5. 枚举示例

▶ 示例 1:枚举变体携带不同类型数据(难度 ⭐⭐)

RUST
// ============================================
// 枚举变体携带数据——Message 类型
// ============================================

#[derive(Debug)]
enum Message {
    Quit,                              // 没有数据
    Move { x: i32, y: i32 },          // 匿名结构体
    Write(String),                     // 元组结构体
    ChangeColor(i32, i32, i32),       // 元组结构体
}

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("Quit: 退出"),
            Message::Move { x, y } => println!("Move: 移动到 ({}, {})", x, y),
            Message::Write(text) => println!("Write: 消息内容: {}", text),
            Message::ChangeColor(r, g, b) => {
                println!("ChangeColor: 颜色 RGB({}, {}, {})", r, g, b);
            }
        }
    }
}

fn main() {
    let messages = vec![
        Message::Write(String::from("Hello")),
        Message::Move { x: 10, y: 20 },
        Message::ChangeColor(255, 0, 0),
        Message::Quit,
    ];

    for msg in &messages {
        msg.call();
    }
}

输出:

TEXT 📖 仅展示
Write: 消息内容: Hello
Move: 移动到 (10, 20)
ChangeColor: 颜色 RGB(255, 0, 0)
Quit: 退出

枚举的每个变体可以携带不同数量和类型的数据。Quit 没有数据,Move 有匿名结构体,Write 有一个 String,ChangeColor 有三个 i32。这是枚举比结构体灵活的地方。


▶ 示例 2:Option<T>——Rust 的空值处理(难度 ⭐⭐)

RUST
// ============================================
// Option<T> 枚举:有值(Some)或无值(None)
// ============================================

// Option 的定义(标准库中)
// enum Option<T> {
//     Some(T),
//     None,
// }

fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None  // 返回"没有值"——而不是崩溃或返回 NaN
    } else {
        Some(a / b)  // 返回"有值"
    }
}

fn main() {
    let result1 = divide(10.0, 2.0);
    let result2 = divide(10.0, 0.0);

    // 用 match 处理 Option
    match result1 {
        Some(value) => println!("10 / 2 = {}", value),
        None => println!("除数为 0"),
    }

    match result2 {
        Some(value) => println!("10 / 0 = {}", value),
        None => println!("除数为 0"),
    }

    // 简化:unwrap 或 expect(有风险,但方便)
    // println!("{}", result2.unwrap());  // ❌ None 上 unwrap 会 panic!
    println!("result1 的值: {}", result1.unwrap());  // ✅ Some 上 unwrap 安全
}

输出:

TEXT 📖 仅展示
10 / 2 = 5
除数为 0
result1 的值: 5

Option<T> 是 Rust 对"可能为空"的标准处理方式。不像其他语言用 null/nil/None 在运行时崩溃,Rust 的 Option<T> 强迫你通过 matchunwrap 显式处理"有值"和"无值"两种情况。


▶ 示例 3:Option 的常用方法(难度 ⭐⭐)

RUST
// ============================================
// Option 的实用方法:map、unwrap_or、expect
// ============================================

fn main() {
    let some_value: Option<i32> = Some(10);
    let none_value: Option<i32> = None;

    // map:如果有值,转换它;没有值,保持 None
    let doubled_some = some_value.map(|x| x * 2);
    let doubled_none = none_value.map(|x| x * 2);
    println!("map 后: {:?}, {:?}", doubled_some, doubled_none);

    // unwrap_or:有值返回值,没有值返回默认值
    println!("unwrap_or: {}, {}", 
        some_value.unwrap_or(0),   // 10
        none_value.unwrap_or(0),   // 0
    );

    // expect:有值返回值,没有值 panic 并显示自定义消息
    println!("expect: {}", some_value.expect("应该有值"));
    // println!("expect: {}", none_value.expect("OH NO! 没有值!"));  // ❌ panic

    // is_some / is_none:判断是否有值
    println!("is_some: {}, is_none: {}", 
        some_value.is_some(), 
        none_value.is_none(),
    );

    // 链式调用示例
    let result = Some(5)
        .map(|x| x + 3)
        .map(|x| x * 2)
        .unwrap_or(0);
    println!("链式调用结果: {}", result);  // 16
}

输出:

TEXT 📖 仅展示
map 后: Some(20), None
unwrap_or: 10, 0
expect: 10
is_some: true, is_none: true
链式调用结果: 16

Option<T> 提供了一系列方法让你用函数式风格链式操作可能为空的值,而不用每次都写 matchmap 转换值、unwrap_or 提供默认值、expect 在调试时提供更清晰的错误信息。


▶ 示例 4:枚举与 match 的穷尽性(难度 ⭐⭐⭐)

RUST
// ============================================
// 枚举专有的 match 穷尽性检查演示
// ============================================

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("幸运便士!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
        // 如果不写 Quarter,编译器会报错:non-exhaustive patterns
    }
}

// 枚举带数据
#[derive(Debug)]
enum IpAddr {
    V4(u8, u8, u8, u8),  // 4 个字节
    V6(String),           // 完整的 IPv6 地址
}

fn main() {
    let coin = Coin::Quarter;
    println!("硬币价值: {} 美分", value_in_cents(coin));

    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

    match home {
        IpAddr::V4(a, b, c, d) => {
            println!("IPv4: {}.{}.{}.{}", a, b, c, d);
        }
        IpAddr::V6(addr) => {
            println!("IPv6: {}", addr);
        }
    }
}

输出:

TEXT 📖 仅展示
硬币价值: 25 美分
IPv4: 127.0.0.1

枚举的每个变体必须在 match 中被处理——这就是"穷尽性检查"。如果你忘记处理某个变体,编译器会报错。这比 C 的 switch 安全得多(C 的 switch 忘记 case 只会默默跳过)。


▶ 示例 5:综合练习——用户输入解析器(难度 ⭐⭐⭐)

RUST
// ============================================
// 综合示例:枚举 + Option + match 实战
// ============================================

#[derive(Debug)]
enum Command {
    Help,
    Greet(String),
    Calc(f64, char, f64),
    Quit,
}

fn parse_command(input: &str) -> Option<Command> {
    let parts: Vec<&str> = input.trim().split_whitespace().collect();
    if parts.is_empty() {
        return None;
    }
    match parts[0] {
        "help" | "h" => Some(Command::Help),
        "quit" | "q" => Some(Command::Quit),
        "greet" if parts.len() > 1 => {
            Some(Command::Greet(parts[1..].join(" ")))
        }
        "calc" if parts.len() == 4 => {
            let a = parts[1].parse::<f64>().ok()?;
            let op = parts[2].chars().next()?;
            let b = parts[3].parse::<f64>().ok()?;
            Some(Command::Calc(a, op, b))
        }
        _ => None,
    }
}

fn execute(cmd: Command) -> bool {
    match cmd {
        Command::Help => {
            println!("命令: help | greet <name> | calc <a> <op> <b> | quit");
            true
        }
        Command::Greet(name) => {
            println!("你好, {}!", name);
            true
        }
        Command::Calc(a, op, b) => {
            let result = match op {
                '+' => Some(a + b),
                '-' => Some(a - b),
                '*' => Some(a * b),
                '/' if b != 0.0 => Some(a / b),
                '/' => { println!("错误: 除数为零"); None }
                _ => { println!("未知运算符: {}", op); None }
            };
            result.map(|r| println!("{} {} {} = {:.2}", a, op, b, r));
            true
        }
        Command::Quit => {
            println!("再见!");
            false
        }
    }
}

fn main() {
    let inputs = [
        "help",
        "greet Alice",
        "calc 10 + 5",
        "calc 20 / 4",
        "calc 1 / 0",
        "unknown",
        "quit",
    ];

    println!("=== 命令解析器 ===");
    for input in inputs {
        println!("\n> {}", input);
        match parse_command(input) {
            Some(cmd) => {
                if !execute(cmd) { break; }
            }
            None => println!("无法识别的命令"),
        }
    }
}

输出:

TEXT 📖 仅展示
=== 命令解析器 ===

> help
命令: help | greet <name> | calc <a> <op> <b> | quit

> greet Alice
你好, Alice!

> calc 10 + 5
10 + 5 = 15.00

> calc 20 / 4
20 / 4 = 5.00

> calc 1 / 0
错误: 除数为零

> unknown
无法识别的命令

> quit
再见!

此示例综合运用了枚举变体携带不同数据、Option 链式操作(ok()?.ok()?)、match 穷尽性检查和 parse_command 返回 Option<Command> 的模式。枚举 + Option + match 是 Rust 中处理"多种可能结果"的标准范式。


❓ 常见问题

Q 枚举和结构体有什么区别?
A 结构体是"同时拥有"(和类型),枚举是"要么是"(或类型)。
Q 为什么 Rust 不用 null 而用 Option?
A Option 是类型安全的——编译器强迫你处理"可能为空"的情况。
Q Some 和 unwrap 有什么区别?
A Some 是"可能有值",unwrap 是"我确定有值,否则崩溃"。
Q 枚举能存多少数据?
A 枚举占用 = 最大变体的大小 + 1 字节的 tag。
Q 怎么给枚举变体的数据命名?
A 用匿名结构体语法。

📖 小节


📝 作业

  1. 难度 ⭐:定义一个枚举 Weekday 包含周一到周日,写一个函数接收 Weekday 返回它是工作日还是周末(用 match)。
  2. 难度 ⭐⭐:写一个函数 fn safe_sqrt(x: f64) -> Option<f64>,如果 x >= 0 返回 Some(x.sqrt()),否则返回 None。在 main 中测试正数和负数。
  3. 难度 ⭐⭐⭐:定义一个枚举 Temperature,包含 Celsius(f64)Fahrenheit(f64) 两个变体。实现方法 fn to_celsius(&self) -> f64fn to_fahrenheit(&self) -> f64。在 main 中创建两种温度并互相转换。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏