文章目录
-
- 一、回顾与导入
- 二、定义枚举
-
- [2.1 简单枚举](#2.1 简单枚举)
- [2.2 带数据的枚举](#2.2 带数据的枚举)
- 三、经典枚举示例:Message
- [四、Option 枚举 ------ 替代 Null](#四、Option 枚举 —— 替代 Null)
-
- [使用 Option](#使用 Option)
- 为什么这是好的设计?
- [五、match 模式匹配](#五、match 模式匹配)
-
- [5.1 基础语法](#5.1 基础语法)
- [5.2 匹配带数据的枚举](#5.2 匹配带数据的枚举)
- [5.3 匹配 Option](#5.3 匹配 Option)
- [5.4 match 必须穷尽所有可能](#5.4 match 必须穷尽所有可能)
- 六、通配符与占位符
-
- [6.1 使用 `_` 通配符](#6.1 使用
_通配符) - [6.2 使用 `_` 忽略不需要的值](#6.2 使用
_忽略不需要的值) - [6.3 使用 `_other` 但保留所有权](#6.3 使用
_other但保留所有权)
- [6.1 使用 `_` 通配符](#6.1 使用
- [七、if let 简洁语法](#七、if let 简洁语法)
-
- [if let + else](#if let + else)
- 八、完整代码示例
- 九、常见陷阱与最佳实践
-
- [陷阱 1:match 忘记处理所有情况](#陷阱 1:match 忘记处理所有情况)
- [陷阱 2:Option 直接使用](#陷阱 2:Option 直接使用)
- 最佳实践
- 十、总结与速查
- 十一、思考题
- 参考链接
深入理解枚举(enum)与强大的模式匹配 ------ Rust 中没有 null 的秘密
一、回顾与导入
上一篇文章我们学习了结构体,它可以将多个数据组合成一个类型。
但有些值可能是多种可能状态中的一种,比如:
- 一个颜色可以是 RGB 或 HSV 格式
- 一个网络请求可能成功返回数据,也可能失败
- 一个值可能存在,也可能不存在
这时候就需要 枚举(enum) 了。
💡 枚举是 Rust 中最强大的类型之一,结合
match模式匹配,可以写出非常安全、语义清晰的代码。
二、定义枚举
2.1 简单枚举
rust
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(four);
route(six);
}
fn route(ip_kind: IpAddrKind) {
match ip_kind {
IpAddrKind::V4 => println!("IPv4 地址"),
IpAddrKind::V6 => println!("IPv6 地址"),
}
}
2.2 带数据的枚举
Rust 的枚举可以像结构体一样携带数据,每个变体可以携带不同类型和数量的数据:
rust
// 标准库中的 IpAddr 就是这样定义的
enum IpAddr {
V4(u8, u8, u8, u8), // 元组形式
V6(String), // 不同变体可以带不同类型
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}
也可以使用结构体作为变体的数据:
rust
struct Ipv4Addr {
a: u8, b: u8, c: u8, d: u8,
}
struct Ipv6Addr {
addr: String,
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
三、经典枚举示例:Message
rust
enum Message {
Quit, // 不带数据
Move { x: i32, y: i32 }, // 匿名结构体
Write(String), // 单个 String
ChangeColor(u8, u8, u8), // 三个 u8
}
impl Message {
fn call(&self) {
match self {
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),
}
}
}
fn main() {
let msg = Message::Write(String::from("Hello, Rust!"));
msg.call();
let msg2 = Message::Move { x: 10, y: 20 };
msg2.call();
}
四、Option 枚举 ------ 替代 Null
Rust 没有 null,取而代之的是 Option<T> 枚举:
rust
enum Option<T> {
None, // 没有值
Some(T), // 包含一个类型为 T 的值
}
使用 Option
rust
fn main() {
let some_number = Some(5);
let some_char = Some('a');
let absent_number: Option<i32> = None; // 必须显式标注类型
// Option<T> 和 T 是不同的类型,不能直接操作
let x: i32 = 5;
let y: Option<i32> = Some(5);
// let sum = x + y; // ❌ 编译错误!不能把 i32 和 Option<i32> 相加
}
为什么这是好的设计?
必须处理 None 的情况,否则编译器会阻止你:
rust
fn main() {
let maybe_num: Option<i32> = Some(42);
// 必须显式处理 None 才能获取内部的值
match maybe_num {
Some(num) => println!("数字是: {}", num),
None => println!("没有数字"),
}
}
这彻底消除了空指针异常这个困扰程序员几十年的问题。
五、match 模式匹配
match 是 Rust 中极其强大的控制流运算符,它将一个值与多个模式进行比较。
5.1 基础语法
rust
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,
}
}
5.2 匹配带数据的枚举
rust
enum Coin {
Quarter(String), // 带一个字符串,表示州名
Other,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Quarter(state) => {
println!("来自 {} 州的 25 分硬币!", state);
25
}
Coin::Other => 0,
}
}
fn main() {
let coin = Coin::Quarter(String::from("California"));
let value = value_in_cents(coin);
println!("价值: {} 美分", value);
}
5.3 匹配 Option
rust
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{:?}, {:?}", six, none);
}
5.4 match 必须穷尽所有可能
Rust 要求 match 必须覆盖所有可能的情况:
rust
fn main() {
let num = Some(5);
// ❌ 编译错误:没有处理 None 的情况
// match num {
// Some(5) => println!("five"),
// }
// ✅ 正确:使用通配符覆盖其他情况
match num {
Some(5) => println!("five"),
_ => println!("other"),
}
}
六、通配符与占位符
6.1 使用 _ 通配符
rust
fn main() {
let dice_roll = 9;
match dice_roll {
3 => println!("add fancy hat"),
7 => println!("remove fancy hat"),
_ => println!("reroll"), // 其他所有值都走这个分支
}
}
6.2 使用 _ 忽略不需要的值
rust
let some_value = Some(42);
match some_value {
Some(_) => println!("有值,但我不关心它是什么"),
None => println!("没有值"),
}
6.3 使用 _other 但保留所有权
_ 会忽略值,如果需要在分支中使用但不想绑定,可以用 _var:
rust
match maybe_value {
Some(_x) => {
// _x 被绑定,但编译器不会警告未使用
}
None => {}
}
七、if let 简洁语法
当你只关心 match 中的一个分支,其他分支想忽略时,if let 更简洁:
rust
fn main() {
let config_max = Some(3u8);
// 用 match 的写法(冗长)
match config_max {
Some(max) => println!("最大值是 {}", max),
_ => (),
}
// 用 if let 的写法(简洁)
if let Some(max) = config_max {
println!("最大值是 {}", max);
}
}
if let + else
rust
enum Coin {
Quarter(String),
Other,
}
fn main() {
let coin = Coin::Quarter(String::from("Texas"));
if let Coin::Quarter(state) = coin {
println!("德州 25 分硬币!");
} else {
println!("不是 25 分硬币");
}
}
八、完整代码示例
rust
// 定义一个表示 Web 事件的枚举
#[derive(Debug)]
enum WebEvent {
PageLoad, // 页面加载
KeyPress(char), // 按键
Click { x: i64, y: i64 }, // 点击(带坐标)
}
impl WebEvent {
fn log(&self) {
match self {
WebEvent::PageLoad => println!("页面加载事件"),
WebEvent::KeyPress(c) => println!("按键事件: '{}'", c),
WebEvent::Click { x, y } => println!("点击事件: 坐标({}, {})", x, y),
}
}
}
// 计算器操作枚举
enum Operation {
Add,
Subtract,
Multiply,
Divide,
}
fn calculate(a: f64, b: f64, op: Operation) -> Option<f64> {
match op {
Operation::Add => Some(a + b),
Operation::Subtract => Some(a - b),
Operation::Multiply => Some(a * b),
Operation::Divide => {
if b == 0.0 {
None // 除以 0 返回 None
} else {
Some(a / b)
}
}
}
}
fn main() {
println!("=== 1. Web 事件处理 ===");
let events = vec![
WebEvent::PageLoad,
WebEvent::KeyPress('a'),
WebEvent::Click { x: 100, y: 200 },
];
for event in &events {
event.log();
}
println!("\n=== 2. Option 示例 ===");
let nums = vec![Some(1), Some(2), None, Some(4)];
let mut sum = 0;
for num in nums {
match num {
Some(n) => sum += n,
None => println!("遇到 None,跳过"),
}
}
println!("总和: {}", sum);
println!("\n=== 3. 计算器示例 ===");
let result = calculate(10.0, 5.0, Operation::Divide);
match result {
Some(v) => println!("10 / 5 = {}", v),
None => println!("除法错误"),
}
let result = calculate(10.0, 0.0, Operation::Divide);
if let Some(v) = result {
println!("10 / 0 = {}", v);
} else {
println!("10 / 0 是未定义的");
}
println!("\n=== 4. if let 链 ===");
let maybe_char = Some('x');
// 多重条件
if let Some('x') = maybe_char {
println!("字符是 x");
} else if let Some('y') = maybe_char {
println!("字符是 y");
} else {
println!("不是 x 也不是 y");
}
}
九、常见陷阱与最佳实践
陷阱 1:match 忘记处理所有情况
rust
enum Color {
Red, Green, Blue
}
fn describe(c: Color) -> &'static str {
match c {
Color::Red => "红色",
Color::Green => "绿色",
// ❌ 忘记处理 Blue -> 编译错误
}
}
陷阱 2:Option 直接使用
rust
fn main() {
let opt: Option<i32> = Some(42);
// ❌ 不能直接使用
// let val = opt + 1;
// ✅ 需要解包
match opt {
Some(v) => println!("{}", v + 1),
None => println!("无值"),
}
}
最佳实践
| 场景 | 推荐做法 |
|---|---|
| 需要处理多个分支 | 使用 match |
| 只关心一个分支 | 使用 if let |
| 需要穷尽检查 | 使用 match + _ |
| 表示可能存在/不存在的值 | 使用 Option<T> |
| 表示可能出错的结果 | 使用 Result<T, E>(下一章) |
十、总结与速查
| 概念 | 语法 | 说明 |
|---|---|---|
| 定义枚举 | enum Name { Variant } |
定义一组可能的变体 |
| 带数据枚举 | Variant(T1, T2) 或 Variant { fields } |
每个变体可携带数据 |
| Option | Option<T> = Some(T) 或 None |
Rust 中没有 null |
| match | match value { pattern => expr } |
模式匹配,必须穷尽 |
| if let | if let pattern = value { expr } |
只匹配一个模式的简洁语法 |
| 通配符 | _ |
匹配所有未列出的情况 |
核心要点:
- ✅ 枚举是 Rust 的代数数据类型(ADT),非常强大
- ✅
Option<T>替代了 null,强制安全处理缺失值 - ✅
match必须穷尽所有情况,防止遗漏 - ✅
if let是match的语法糖,用于单模式匹配 - ✅ 枚举和
match结合是 Rust 中最常见的模式之一
十一、思考题
- 以下代码会输出什么?
rust
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(5) => println!("匹配到 5"),
Some(50) => println!("匹配到 50"),
_ => println!("其他"),
}
}
-
为什么 Rust 选择用
Option<T>而不是null? -
if let和match分别应该在什么场景下使用?