📚 Rust 课程系列

  1. 课程概览
  2. 基础语法
  3. 函数与输入输出
  4. 所有权、借用与生命周期
  5. 结构体、枚举与模式匹配(本文)
  6. 类型系统:泛型、trait 与多态
  7. 集合与容器
  8. 错误处理与 Panic 恢复
  9. 模块、属性与宏
  10. 智能指针、迭代器与闭包
  11. 并发与异步编程
  12. Unsafe Rust 与常用 trait 详解
  13. 工具链、Cargo 与外部 crate
  14. 最佳实践、性能与调试

结构体与枚举是 Rust 自定义类型的两大支柱:结构体把数据「组合」在一起,枚举把「互斥的几种可能」表达为一个类型。模式匹配则是对枚举进行分支处理的惯用方式,配合穷尽性检查,让漏处理的分支在编译期就暴露出来。本章把它们放在一起,因为枚举几乎总是和 match 搭配使用。

结构体与枚举

结构体

Rust 提供三种结构体形式:

形式语法典型用途
命名结构体struct Name { field: T }数据记录、业务实体
元组结构体struct Name(T1, T2)新类型包装(newtype)、坐标/颜色
单元结构体struct Name;实现 trait 但无需持有数据
1
2
3
4
5
6
struct Rectangle { width: u32, height: u32 }  // 命名
struct Color(u8, u8, u8); // 元组
struct AlwaysEqual; // 单元

let rect = Rectangle { width: 30, height: 50 };
let black = Color(0, 0, 0);

🔄 对比:Rust 结构体没有继承,组合通过字段嵌套或 trait 实现。这避免了 C++ 菱形继承和 Java 深层继承链的问题——Rust 用 trait 共享行为,用组合共享数据。

方法与关联函数:使用 impl 块定义。不可变操作用 &self,需要修改用 &mut self;不取 self 参数的是关联函数(类似其他语言的「静态方法」),常作为构造器。

1
2
3
4
5
6
7
impl Rectangle {
fn area(&self) -> u32 { self.width * self.height } // 方法
fn square(size: u32) -> Self { // 关联函数
Self { width: size, height: size }
}
}
let sq = Rectangle::square(10);

💡 提示:一个类型可以写多个 impl 块,Rust 会自动合并。关联函数通过 Type::method() 调用(如 String::fromVec::new)。

打印结构体:最常见的是派生 Debug,然后用 {:?}{:#?} 输出:

1
2
3
4
5
#[derive(Debug)]
struct Point { x: i32, y: i32 }
let p = Point { x: 1, y: 2 };
println!("{:?}", p); // Point { x: 1, y: 2 }
println!("{:#?}", p); // 多行美化输出

⚠️ 注意:结构体默认拥有其字段;若要持有引用则需生命周期参数(详见《所有权、借用与生命周期》)。编译器不会让你忘记——尝试存引用而不标注生命周期会直接报错。

枚举

枚举的每个变体可以携带不同类型、不同数量的数据,这让它比「带 tag 的结构体」更灵活:

1
2
3
4
5
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 命名字段
Write(String), // 元组式
}

🔄 对比:Rust 枚举 ≈ 函数式语言的 ADT(代数数据类型),远比 C/Java 枚举强大——C 枚举只是整数常量,Java 枚举是单例类,而 Rust 枚举每个变体可以携带完全不同的数据。

标准库核心枚举Option<T>Result<T, E> 本质上是:

1
2
enum Option<T> { Some(T), None }
enum Result<T, E> { Ok(T), Err(E) }

Option 取代了「空指针」:凡是可以缺失的值都用 Option<T> 表达,编译器强制你处理 None,从而消除了一整类空指针解引用错误。Result 的深入用法见《错误处理与 Panic 恢复》

💡 提示:枚举非常适合状态机建模(连接状态、任务状态等),结合 match 可保证覆盖所有分支。新增变体后,所有 match 都会被编译器提醒补全——这是穷尽性检查的核心价值。

枚举也能定义方法:

1
2
3
4
5
6
7
8
9
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("quit"),
Message::Move { x, y } => println!("move to ({}, {})", x, y),
Message::Write(s) => println!("write {}", s),
}
}
}

常见模式——带数据的枚举

1
2
3
4
5
6
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));

⚠️ 注意:DTO 与业务实体要区分——接口层返回轻量 DTO,避免将内部可变状态或锁直接暴露给调用者。

模式匹配

match 与 if let

构造特点适用场景
match穷尽匹配,必须覆盖所有情况枚举分支、多路分发
if let只匹配一种模式,其余忽略只关心单一变体
while let循环匹配,失败则退出逐项解析(如 stack.pop()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// match:穷尽 + 守卫条件
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("Even: {}", x),
Some(x) => println!("Odd: {}", x),
None => println!("None"),
}

// if let:只关心一种情况
if let Some(x) = num {
println!("Value: {}", x);
}

// while let:逐项消费
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("弹出: {}", top);
}

💡 提示match 是表达式,每个分支返回的值类型必须一致。当你只关心一种情况时,if let 更轻量,但放弃了穷尽性检查——未来新增枚举变体时不会提醒你。

⚠️ 注意:当有默认分支时尽量显式写出 _ => {},以便未来扩展时不遗漏处理。在匹配复杂结构时使用解构({ x, y, .. }(_, _, tail)),并用 _/.. 忽略不必要字段。

解构与绑定

模式匹配可以同时解构结构体、元组、引用,并用 @ 把值绑定到变量同时测试范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Point { x: i32, y: i32 }

let p = Point { x: 3, y: 7 };
match p {
Point { x, y: 0 } => println!("在 x 轴上: {}", x),
Point { x: 0, y } => println!("在 y 轴上: {}", y),
Point { x, y } => println!("({}, {})", x, y),
}

// @ 绑定:既测试范围,又保留值
let age: i32 = 25;
match age {
n @ 0..=12 => println!("儿童 {}", n),
n @ 13..=17 => println!("少年 {}", n),
n => println!("成年 {}", n),
}

解构也常出现在函数参数和 let 语句中:

1
2
3
4
fn print_coords(&(x, y): &(i32, i32)) {
println!("x = {}, y = {}", x, y);
}
let (a, b, c) = (1, 2, 3); // let 解构

💡 提示@ 绑定的价值在于「既想对值做范围/模式测试,又想保留整个值供后续使用」,否则测试后只能拿到解构出的子部分。

let-else

Rust 1.65 引入的 let else 适合「匹配失败就提前返回/报错」的场景,比 if let 更适合做早期校验:

1
2
3
4
5
6
7
// 早期返回:None 时直接退出
fn first_word_len(text: &str) -> usize {
let Some(first) = text.split_whitespace().next() else {
return 0; // else 块必须 diverge(return/break/panic!)
};
first.len() // 匹配成功后 first 进入作用域
}

⚠️ 注意let PATTERN = expr else { ... }else 块必须 diverge——即 returnbreakcontinuepanic! 等不返回的正常路径。匹配成功时绑定直接进入后续作用域,无需额外缩进,这是它比 if let + else 更优雅的原因。

🔄 对比let-else ≈ Go 的 if err != nil { return err } 模式,但类型安全——编译器保证不匹配时你一定会退出,不会意外 fall through。

模式匹配的完整能力(进阶)

Rust 模式匹配远不止 matchif let,以下是一个快速参考:

模式位置示例说明
match 分支match x { A => .. }穷尽匹配
if let / while letif let Some(v) = x单一模式
let 语句let (a, b) = tuple解构绑定
let-elselet Some(v) = x else { return }早期返回
函数参数fn f(&(x, y): &(i32, i32))参数解构
for 循环for (k, v) in map迭代解构
守卫条件Some(x) if x > 0附加条件过滤
@ 绑定n @ 0..=12测试 + 绑定
.. 忽略Point { x, .. }忽略剩余字段

🔬 进阶:Rust 的模式分为不可反驳的(irrefutable,如 let (a, b) = (1, 2) 必定成功)和可反驳的(refutable,如 if let Some(x) = opt 可能失败)。let 和函数参数只接受不可反驳模式;match/if let/while let 接受可反驳模式。混用会直接编译报错。