Rust: Rust 枚举(Enum)与 Option:模式匹配的完美搭档
最后更新:2026-08-26
枚举(Enum)是 Rust 最强大的类型之一——它让一个值可以是一组可能变体中的某一个,且每个变体可以携带不同的数据。
如果说结构体是"和"类型(同时拥有所有字段),那枚举就是"或"类型(要么是这个,要么是那个)。
1. 你将学到
- 定义枚举类型和变体(variant)
- 枚举变体可以携带不同类型的数据
- 用
impl给枚举添加方法 Option<T>枚举——Rust 的"空值"方案- 枚举与 match 的穷尽性检查
Option<T>的常用方法(unwrap、map、expect)
2. 概念图解
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 种状态:
0 = 待支付, 1 = 已支付, 2 = 已发货, 3 = 已完成
let order_status = 0; // 待支付
// 但万一有人写出 order_status = 99 呢?
// 或者把状态和品数量搞混:let order_status = product_count;
用整数表示状态是 C 语言的老套路——它不 type-safe。任何整数都可以赋值给"状态",编译器不会帮你检查。而且状态之间没有关联,易出错。
(2) 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) 枚举变体可以携带数据
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 知道当前存储的是哪个变体。
graph TB
subgraph "Message 枚举在内存中"
TAG[tag: 1 字节] -->|标识当前是哪个变体| LABEL
DATA[最大变体的数据: 8 字节] --> LABEL[一共 9 字节]
end
5. 枚举示例
▶ 示例 1:枚举变体携带不同类型数据(难度 ⭐⭐)
// ============================================
// 枚举变体携带数据——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();
}
}
输出:
Write: 消息内容: Hello
Move: 移动到 (10, 20)
ChangeColor: 颜色 RGB(255, 0, 0)
Quit: 退出
枚举的每个变体可以携带不同数量和类型的数据。
Quit没有数据,Move有匿名结构体,Write有一个 String,ChangeColor有三个 i32。这是枚举比结构体灵活的地方。
▶ 示例 2:Option<T>——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 安全
}
输出:
10 / 2 = 5
除数为 0
result1 的值: 5
Option<T>是 Rust 对"可能为空"的标准处理方式。不像其他语言用null/nil/None在运行时崩溃,Rust 的Option<T>强迫你通过match或unwrap显式处理"有值"和"无值"两种情况。
▶ 示例 3:Option 的常用方法(难度 ⭐⭐)
// ============================================
// 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
}
输出:
map 后: Some(20), None
unwrap_or: 10, 0
expect: 10
is_some: true, is_none: true
链式调用结果: 16
Option<T>提供了一系列方法让你用函数式风格链式操作可能为空的值,而不用每次都写match。map转换值、unwrap_or提供默认值、expect在调试时提供更清晰的错误信息。
▶ 示例 4:枚举与 match 的穷尽性(难度 ⭐⭐⭐)
// ============================================
// 枚举专有的 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);
}
}
}
输出:
硬币价值: 25 美分
IPv4: 127.0.0.1
枚举的每个变体必须在 match 中被处理——这就是"穷尽性检查"。如果你忘记处理某个变体,编译器会报错。这比 C 的
switch安全得多(C 的 switch 忘记 case 只会默默跳过)。
▶ 示例 5:综合练习——用户输入解析器(难度 ⭐⭐⭐)
// ============================================
// 综合示例:枚举 + 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!("无法识别的命令"),
}
}
}
输出:
=== 命令解析器 ===
> 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 中处理"多种可能结果"的标准范式。
❓ 常见问题
📖 小节
- 枚举(Enum) 是一种"或类型"——一个值可以是多个变体中的某一个
- 枚举变体可以携带数据(元组结构体或匿名结构体形式)
Option<T>是标准库中最常用的枚举,替代 null- 枚举与
match配合,编译器强制穷尽性检查 Option提供map、unwrap_or、expect等链式方法- 枚举的内存占用 ≈ 最大变体 + 1 字节 tag
📝 作业
- 难度 ⭐:定义一个枚举
Weekday包含周一到周日,写一个函数接收Weekday返回它是工作日还是周末(用 match)。 - 难度 ⭐⭐:写一个函数
fn safe_sqrt(x: f64) -> Option<f64>,如果 x >= 0 返回Some(x.sqrt()),否则返回None。在main中测试正数和负数。 - 难度 ⭐⭐⭐:定义一个枚举
Temperature,包含Celsius(f64)和Fahrenheit(f64)两个变体。实现方法fn to_celsius(&self) -> f64和fn to_fahrenheit(&self) -> f64。在main中创建两种温度并互相转换。