模式匹配:match、解构与绑定
📚 Rust 课程系列
模式匹配远不止是 C/Java 里 switch 的升级版——switch 只能对标量值做相等比较,而 Rust 的模式匹配能同时完成「判断」与「拆解」两件事:一边判断一个值属于枚举的哪个变体,一边把这个变体内部的数据解构出来绑定到变量。配合穷尽性检查,编译器会逼着你处理每一种可能,让「漏写一个分支」这类 bug 在编译期就暴露无遗。
本章承接《枚举》——枚举定义了「互斥的几种可能」,而模式匹配就是处理这些可能的惯用手段。
模式匹配是什么:从「判断」到「拆解」
在很多语言里,「判断一个值是哪种情况」和「从值里取出数据」是两件分开的事。以 TypeScript 为例:
1 | |
这里「判断」用 if (s.kind === ...),「取数据」靠 TS 的类型收窄(narrowing)隐式完成。Rust 把这两步合并成一个语法动作——模式既描述了「值长什么样」,又声明了「把哪些部分绑定到哪些变量」:
1 | |
Shape::Circle { r } 这个模式既匹配 Circle 变体,又把内部的 r 字段绑定到同名变量。这就是模式匹配的核心:一个模式 = 一个形状断言 + 一组绑定声明。
💡 提示:模式匹配的「判断 + 拆解合一」并非 Rust 首创,它继承自 ML/Haskell/OCaml 的函数式传统。Rust 的独到之处在于把它和所有权、借用深度耦合——模式不仅能解构值,还能决定是「移动」还是「借用」内部数据,这既是威力来源,也是陷阱高发区。
match:穷尽式匹配的基石
match 是 Rust 模式匹配的主力构造。它有四条核心规则:是表达式、必须穷尽、各分支类型一致、按顺序求值。
match 是表达式
与 C/Java 的 switch(语句)不同,match 是表达式,它会求值出一个结果。这意味着 match 可以出现在任何期望值的位置——赋值右侧、函数返回值、函数参数:
1 | |
因为 match 求值,所以分支末尾不写逗号以外的结束符,也不能用 break。每个分支 => 右侧是一个表达式,求值后即成为该分支的结果。需要执行多条语句时用代码块,块的最后一行作为值:
1 | |
🔄 对比:C/Java 的
switch是语句,不能直接求值;C# 8.0+ 的switch表达式才追上了这一点。Python 3.10 的match语句也偏向语句式,需手动赋值。Rust 从一开始就让match是表达式,这和它「面向表达式」的语法哲学一致(见《基础语法(二)》)。
穷尽性检查
match 必须覆盖被匹配类型的所有可能,否则编译失败。这是 Rust 替代「漏写 default 分支」这一类 bug 的根本机制:
1 | |
当你后来给 Coin 新增一个变体 Dollar,所有 match Coin 的地方都会编译失败——编译器会一一列出未覆盖的位置。这种「破坏性变更在编译期可见」的特性,是枚举 + match 组合的最大价值。
穷尽性对布尔值也成立:match bool { true => .. } 会因为漏掉 false 而报错。对没有 | 合并的整数字面量则无法穷尽,必须用 _ 或范围兜底:
1 | |
⚠️ 注意:
_通配符会吞掉所有未列出的情况。一旦写了_,未来新增枚举变体时编译器不再提醒你。所以对自家枚举,优先显式列出每个变体;只在真正「其余情况等价」时才用_,并且最好在旁边留一行注释说明意图。
各分支返回类型必须一致
既然 match 求值出一个结果,所有分支的结果类型必须相同——否则编译器不知道整个 match 该是什么类型:
1 | |
需要返回「不同类型的结果」时,通常说明你该用一个枚举把可能性编码进类型,而不是靠动态分发。例如解析器返回不同 AST 节点,正是枚举的用武之地。
分支类型一致是静态要求,与运行时实际走哪个分支无关。即便某个分支在运行时永远走不到,它的类型仍参与推断。
守卫条件
守卫(guard)给分支附加一个布尔过滤,让你在「形状匹配」之外再加一层「值条件」:
1 | |
守卫是 if 后跟一个 bool 表达式。它让一个模式可以「匹配但被守卫拒绝」——此时该分支不算命中,继续尝试下一个分支。这带来一个微妙但关键的后果:带守卫的分支不再保证穷尽性。编译器无法静态判断守卫是否覆盖所有情况,因此下例能编译:
1 | |
若去掉最后的 _,编译会失败——因为 Some(0) 不满足任何守卫,落入未覆盖。但编译器的判断是「所有带守卫的分支都可能不命中」,所以你必须补一个兜底。守卫的更多限制见后文「守卫条件进阶」一节。
分支顺序与默认分支 _
match 按从上到下的顺序尝试匹配,第一个命中的分支胜出。因此更具体的模式必须写在更通用的模式之前:
1 | |
_ 是「通配符」模式,匹配任何值且不绑定变量。它通常放在最后作为默认分支。Rust 编译器会警告「_ 之后的分支永远不会命中」(unreachable_patterns):
1 | |
除了 _,可用「或模式」| 列出多个具体值作为一组。但 _ 仍是覆盖剩余的唯一通配手段。
💡 提示:写默认分支时,与其用
_ => {}静默忽略,不如用_ => unreachable!("说明原因")或_ => panic!(...)显式声明「这里不该发生」。这样若未来枚举扩展意外落入此分支,能在运行时立刻暴露,而不是悄无声息地跳过。
match 的求值与绑定作用域
match 求值时,只有被命中的那个分支的 => 右侧会被执行,其余分支的表达式完全不求值。分支内绑定的变量只在该分支的作用域内有效:
1 | |
分支之间互不影响:一个分支里 move 走的值不会影响其他分支的可用性,因为只有命中分支才真正执行 move。
if let 与 while let:放弃穷尽性的轻量选择
match 强大但有时过重——当你只关心一种情况、其余全忽略时,写一整张 match 表加 _ => {} 显得啰嗦。if let 和 while let 就是为这种场景准备的快捷语法。
if let:单一模式匹配
if let 把「匹配一种模式,否则走 else」浓缩成一行:
1 | |
if let PATTERN = EXPR { ... } 在 PATTERN 匹配 EXPR 时执行块,绑定的变量在块内可用。else 块可选,在未匹配时执行。
if let 也可以链式 else if let,构成多路「试探」:
1 | |
不过这种写法放弃了穷尽性——未来给 Event 加变体,编译器不会提醒。此时用 match 更安全。
⚠️ 注意:
if let最大的代价是放弃穷尽性检查。当被匹配的类型是自家枚举且变体可能增加时,优先用match,让编译器替你盯着新增分支。只有匹配外部类型(如Option/Result,变体稳定不会变)或确实只关心单一情况时,if let才是合适的选择。
while let:逐项消费
while let 是 if let 的循环版:每次迭代先尝试匹配,成功则执行循环体并继续,失败则退出循环。它天然适合「逐项消费」迭代器或栈结构:
1 | |
while let Some(_) = iter.next() 与 for item in iter 在效果上接近,但 while let 更灵活——你可以控制消费的步长,或在循环体里修改被消费的数据源:
1 | |
💡 提示:
while let与for的取舍——for适用于「遍历全部」;while let适用于「按条件消费,可能提前结束或需要手动控制迭代」。注意while let Some(x) = col.pop()会消费集合(取出所有权),而for x in &col只是借用。
何时该用 if let 而非 match
下表归纳三种构造的取舍:
| 构造 | 穷尽性 | 返回值 | 典型场景 |
|---|---|---|---|
match | 强制穷尽 | 是(表达式) | 多路分发、自家枚举 |
if let | 放弃 | 否(语句) | 只关心一种变体 |
while let | 放弃 | 否(语句) | 逐项消费、循环匹配 |
flowchart TD
Q[需要匹配枚举?] -->|是| A{关心几种变体?}
Q -->|否, 只是 Option/Result| B{只关心一种?}
A -->|全部| M[用 match: 穷尽安全]
A -->|一种| C{变体未来会增吗?}
C -->|会| M
C -->|不会, 如外部类型| I[if let 更轻量]
B -->|是| I
B -->|否| M
A -->|循环消费| W[while let]let-else:早期返回的优雅写法
Rust 1.65 引入的 let-else 专为「匹配失败就提前返回」设计,是写「前置校验」最顺手的工具。
基本语法与 diverge 要求
语法是 let PATTERN = EXPR else { ... };:匹配成功时,绑定的变量在后续作用域可用;匹配失败时,执行 else 块——而 else 块必须 diverge(不正常返回):
1 | |
else 块里必须以 return、break、continue、panic!、todo!()、unreachable!() 或无限循环等「diverging 表达式」收尾。否则编译失败:
1 | |
对比 Go 的 if err != nil
let-else 在心智上最接近 Go 的「错误即提前返回」惯用法:
1 | |
1 | |
🔄 对比:Go 的
if err != nil { return err }是约定俗成的模式,但编译器不强制——你完全可以忘了 return,让控制流「掉下去」。Rust 的let-else由编译器保证:不匹配时你必须 diverge,不会意外 fall through 继续执行后续代码。这是「类型系统替你守住不变式」的又一例。
与 if let + else 的对比
不用 let-else 时,「匹配失败就返回」要用 if let 加一层缩进:
1 | |
当校验有多步时,if let 嵌套会让代码不断右移,形成「箭头形」缩进:
1 | |
let-else 把每一步校验拍平成「失败就退出」,主体逻辑始终在顶层缩进:
1 | |
💡 提示:
let-else的核心价值是「让 happy path 保持在最小缩进层」。每一步前置校验失败就提前退出,主流程像一条直线。这种写法在解析器、命令行参数处理、HTTP 请求校验等「层层把关」场景下尤其清爽。
模式的完整能力总览
Rust 的「模式」是一套完整的微型语言。下表先给出全貌,后续各节逐一展开:
| 模式类别 | 示例 | 说明 |
|---|---|---|
| 字面量 | 1、true、"hi" | 按值相等匹配 |
| 变量绑定 | x | 匹配任意值并绑定到 x |
| 通配符 | _ | 匹配任意值,不绑定 |
| 或模式 | 1 | 2 | 3 | 匹配任一子模式 |
| 范围 | 1..=10、'a'..='z' | 闭区间匹配 |
| 结构体解构 | Point { x, y } | 按字段解构 |
| 元组解构 | (a, b, c) | 按位置解构 |
| 枚举解构 | Some(x)、Ok(v) | 按变体解构 |
| 数组/切片 | [a, b, ..] | 按元素解构 |
| 引用 | &x、&(a, b) | 解引用匹配 |
@ 绑定 | n @ 0..=10 | 测试范围同时绑定 |
| 忽略剩余 | .. | 忽略其余字段/元素 |
ref/ref mut | ref x | 显式借用绑定 |
字面量、变量绑定与通配符
字面量模式按值相等匹配,支持整数、浮点、布尔、字符、字符串:
1 | |
注意浮点字面量匹配因精度问题不推荐用于 match——浮点相等比较本身不可靠。
变量绑定模式匹配任意值并把值绑定到该变量名。它「吞噬一切」,所以必须放在最后,否则后面的分支不可达:
1 | |
变量绑定和字面量同名时会引发「遮蔽」陷阱:模式里的 x 是新绑定,而非外部变量。要引用外部变量做相等比较,必须用守卫或限定路径:
1 | |
⚠️ 注意:这是模式匹配里最经典的陷阱之一——模式中的标识符默认是「新绑定」而非「比较外部值」。Rust 1.x 时代曾有
@绑定的相关讨论。要按外部变量做相等匹配,用守卫x if x == target,或把外部值用const/路径限定(如Enum::Variant这种路径模式才按相等匹配)。
通配符 _ 是「不绑定任何变量的变量模式」。它匹配任意值并丢弃,常作默认分支。_ 与变量绑定的区别仅在于:_ 不发生 move(即使匹配的是非 Copy 类型),因为编译器知道你不用它:
1 | |
若写成 Some(x),则 x 会 move 走 String,s 之后就不可用了。
或模式与范围模式
或模式用 | 把多个模式合并为一个,匹配其中任一即可。| 优先级低于 ..=,且各子模式绑定的变量必须「同型同数量」:
1 | |
或模式中各分支绑定的变量类型必须相同、数量必须相同,否则编译失败。| 还可与 @ 范围组合:n @ (1..=5 | 10..=15)。
范围模式用 ..= 表示闭区间,支持整数与字符(char)。开区间 .. 自 Rust 1.80 起在模式中可用(之前只能 ..=):
1 | |
💡 提示:范围模式对整数仍无法穷尽(
u32有 40 亿个值),所以即便你列了0..=150,还得补_或确保覆盖全部取值。范围模式主要用于char与有界的判别值,不要指望它替代match的穷尽性。
解构详解
解构是模式匹配最强大的能力之一:把组合类型「拆开」并同时绑定内部数据。Rust 几乎所有复合类型都能解构——结构体、元组、枚举、数组、切片、引用。
结构体解构
结构体按字段名解构,未列出的字段用 .. 忽略:
1 | |
字段模式 y: 0 表示「y 字段必须等于 0 才匹配,且不绑定」;y 简写等价于 y: y(绑定到同名变量)。.. 忽略所有未列出的字段,一个模式里只能出现一次 ..。
字段名还可做更细的子模式:
1 | |
元组解构
元组按位置解构,_ 跳过单个元素,.. 跳过中间若干元素:
1 | |
let 语句里解构元组是最常见的不可反驳模式:
1 | |
枚举变体解构(含嵌套)
枚举解构是 match 的主战场。解构时必须写出完整路径 Enum::Variant,并按变体定义的数据形状绑定:
1 | |
枚举变体有三种数据形状,对应三种解构语法:
| 变体形状 | 定义 | 解构语法 |
|---|---|---|
| 单元变体 | Quit | Message::Quit |
| 元组变体 | Write(String) | Message::Write(s) |
| 结构体变体 | Move { x, y } | Message::Move { x, y } |
嵌套枚举同样可层层解构。一个表达式语法树是典型例子:
1 | |
更多枚举变体的定义与内存布局见《枚举》。
数组与切片解构
定长数组按元素解构,长度必须精确匹配:
1 | |
数组解构的长度是类型的一部分:[i32; 3] 与 [i32; 4] 是不同类型,模式必须写对长度。切片解构则更灵活,.. 可匹配任意长度,常用于「取首/尾元素」。
💡 提示:
[first, ..]与slice.first()等价但更直接。在解析固定头部 + 变长尾部的二进制协议时,[cmd, payload @ ..]这种写法极为顺手——@把可变切片绑定到payload。
引用解构:& 与 &mut
模式里的 & 解引用匹配,&mut 匹配可变引用:
1 | |
&(a, b) 把 &(i32, i32) 解构出 a: i32、b: i32。这是「显式」写法。实际中更常用「匹配人体工程学」自动处理引用,见下一节。
多层嵌套解构
模式可任意深度嵌套,把复杂结构一次性拆到原子:
1 | |
嵌套解构让「深挖特定形状」的代码极为紧凑,但可读性需权衡——过深的嵌套会让模式难以一眼看懂,此时拆成多个 match 或用辅助函数更清晰。
绑定模式与 match ergonomics
模式匹配与引用的交互是 Rust 独有的难点。
ref / ref mut 显式绑定
默认情况下,模式中的变量按「值」绑定——会 move 出非 Copy 数据。若你只持有引用却想「借用」内部数据,需用 ref/ref mut 显式声明:
1 | |
若想保留 opt 的所有权,用 ref 借用:
1 | |
ref s 表示「把匹配到的值以 & 形式绑定到 s」。ref mut 是可变借用版本,要求被匹配值本身是 mut:
1 | |
⚠️ 注意:
ref写在模式里(绑定处),&写在类型/值处。Some(ref s)与Some(&s)含义完全不同:前者「借用匹配到的值」,后者「匹配一个引用」。混淆它们是新手常犯错误。
匹配人体工程学(match ergonomics)
Rust 2018 引入「匹配人体工程学」(match ergonomics,RFC 2008),让你在匹配引用时省略 &/ref,编译器自动推断「默认绑定模式」。对比两种写法:
1 | |
当被匹配值是引用时,编译器进入「默认绑定模式」:模式中的变量默认以引用形式绑定,自动调整成合适的 &/&mut/ref。规则大致是:
- 匹配
&T时,模式里的字段默认绑定为&T的字段的引用; - 匹配
&mut T时,按需绑定为&mut或共享引用。
1 | |
这套机制让 90% 的场景无需手写 ref/&,代码更干净。
默认绑定模式演进
「默认绑定模式」会随匹配值的引用性「传染」到子模式。理解它的关键是:每一步匹配都会更新当前的「绑定模式」(默认 move,遇到 & 变 ref,遇到 &mut 变 ref mut):
flowchart TD
S[被匹配值类型] --> V{是引用?}
V -->|否, 拥有所有权| M[默认 move 绑定]
V -->|是 &T| R[默认 ref 绑定]
V -->|是 &mut T| RM[默认 ref mut 绑定]
M --> D[子模式按 move 解构]
R --> DR[子模式按 ref 解构]
RM --> DRM[子模式按 ref mut 解构]
D --> B[变量类型 = 字段类型]
DR --> B2[变量类型 = &字段类型]
DRM --> B3[变量类型 = &mut 字段类型]实践中你不必手动追踪这套状态——写出模式让编译器跑一遍,错误信息会指引你。但理解原理能帮你读懂「为什么这里 x 是 &i32 而不是 i32」。
🔬 进阶:match ergonomics 引入前,匹配引用必须处处写
&/ref,代码冗长易错。RFC 2008 之后,ref几乎只出现在「匹配拥有所有权的值却想借用」的少数场景。但当你显式写ref时,它会覆盖默认绑定模式,这在混合move与borrow的复杂 match 里仍有用武之地。
@ 绑定:测试的同时保留值
@ 绑定让你在用范围或模式「测试」一个值的同时,把整个值绑定到一个变量。没有 @,测试通过后你只能拿到解构出的子部分。
范围 + 绑定
最常见的用法是范围测试 + 绑定:
1 | |
若不用 @,0..=12 匹配后你拿不到具体的 n 值(除非用 if 守卫),只能知道「在范围内」。@ 把「在范围内」与「具体是多少」一次性拿到。
子绑定
@ 还可在解构子结构时绑定中间值。比如匹配 Option 同时保留整个 Option 和内部值:
1 | |
这在错误处理时很有用——你想既记录「整个 Result」,又取出内部的错误信息。
绑定同时测试值
@ 与或模式、范围组合可表达复杂条件:
1 | |
💡 提示:
@的本质是「为子模式命一个名字」。当你既想用模式断言形状,又想保留被断言的值,@就是答案。它在解析器(保留 token 同时检查类型)、校验逻辑(保留原值同时检查范围)里尤为常用。
守卫条件进阶
守卫(if 子句)扩展了模式的表达力,但也带来若干限制与陷阱。
附加布尔过滤
守卫是分支的布尔过滤条件,可引用分支内绑定的变量:
1 | |
守卫可访问外部变量,这让模式能依赖运行时上下文:
1 | |
守卫的限制
守卫有几个不那么直观的限制,是陷阱高发区:
1. 绑定仅在分支内有效。守卫里能用分支绑定的变量,但守卫求值发生在「模式匹配成功后、分支体执行前」,绑定的变量生命周期仅限本分支:
1 | |
2. 守卫可能被多次求值。编译器在某些优化下可能对守卫求值不止一次,因此守卫不应有副作用。下例是反面教材:
1 | |
实际上守卫只接受 bool 表达式,不能用块语句做副作用(块若返回 bool 形式上可行但极不推荐)。规范用法是把副作用放在分支体里。
3. 守卫破坏穷尽性保证。带守卫的分支「可能不命中」,因此编译器对带守卫的 match 更保守——通常仍要求一个不带守卫的兜底分支:
1 | |
4. 守卫不能引用未绑定的模式变量。| 或模式里的变量在守卫里可见,但若同一分支的 | 两侧绑定不同变量,守卫里用哪个就成问题:
1 | |
⚠️ 注意:守卫是 Rust 模式匹配里最容易踩坑的部分。原则是:守卫只做纯判断,不依赖求值次数、不带副作用、不假设穷尽性。需要复杂判断时,把逻辑挪到分支体里用普通
if表达,比塞进守卫更清晰。
可反驳 vs 不可反驳模式
模式分两大类:不可反驳的(irrefutable)和可反驳的(refutable)。这一区分决定了模式能用在哪些位置。
定义
- 不可反驳模式:对任何输入都匹配成功。如
x(变量绑定)、_、(a, b)(元组解构,前提是类型确实是二元组)、Point { x, y }(结构体解构,前提类型是Point)。 - 可反驳模式:对某些输入可能匹配失败。如
Some(x)(可能是None)、Ok(v)(可能是Err)、1(值可能不是 1)、&(_, _)。
1 | |
哪些位置接受哪种
不同位置对模式类型有不同要求:
| 位置 | 接受的模式 | 示例 |
|---|---|---|
let 语句 | 仅不可反驳 | let (a, b) = t; |
| 函数参数 | 仅不可反驳 | fn f((a, b): (i32, i32)) |
for 循环 | 仅不可反驳 | for (k, v) in map |
match 分支 | 可反驳(至少一个能匹配) | match opt { Some(x) => .. } |
if let | 可反驳 | if let Some(x) = opt |
while let | 可反驳 | while let Some(x) = s.pop() |
let-else | 可反驳 | let Some(x) = opt else {..} |
| 闭包参数 | 仅不可反驳 | |(a, b)| .. |
混用会编译报错
在只接受不可反驳模式的位置写可反驳模式,编译器直接拒绝:
1 | |
编译器会建议改用 if let 或 let else:
1 | |
反过来,if let 必须用可反驳模式——写不可反驳模式会得到「无意义」警告:
1 | |
💡 提示:理解可反驳/不可反驳的分界,能帮你快速定位「为什么这里编译器让我改用
if let」。规则很简单:let/函数参数/for只接受「必成功」的模式,因为它们没有「失败时怎么办」的语法;match/if let/while let/let-else都有失败处理路径,故接受可反驳模式。
模式的限制与陷阱
模式匹配与所有权、借用深度耦合,催生了一批独特的陷阱。
移动语义在 match 中的影响
match 一个拥有所有权的枚举时,命中的分支会按模式绑定「move」出内部数据。这会消耗被匹配的值:
1 | |
若多个分支都想 move 不同的内部数据,看起来像「同一个值被 move 多次」,实则安全——因为只有命中分支才真正执行:
1 | |
但若分支只用 _ 忽略,则不 move,原值仍可用(前文通配符已述)。问题出在「想同时 move 部分字段、保留其他」时——枚举是整体,无法「只 move 一个字段」。这时要么 match 借用,要么用 take 之类的方法换出内部值。
as_ref 惯用法
当 Option/Result 持有非 Copy 数据(如 String),直接 match 会消耗它。若你只想「看一下」内部值,用 as_ref 先借用:
1 | |
as_ref 把 Option<T> 转成 Option<&T>,as_mut 转成 Option<&mut T>,as_deref 进一步转成 Option<&<T as Deref>::Target>(如 Option<&String> → Option<&str>)。这套方法是与所有权协作的核心惯用法,呼应《所有权、借用与生命周期》。
1 | |
守卫副作用与多次求值
前文提到守卫可能被多次求值。这不仅是「别写副作用」的告诫,还有更微妙的后果:带守卫的分支不能依赖「至多求值一次」。例如用守卫做带状态的过滤,结果可能不可预期。规范做法是把判断移入分支体,用普通 if 表达。
绑定模式与 move 的冲突
当一个模式既想绑定整个值(@)又想 move 子部分,会与所有权规则冲突:
1 | |
这种「整体 + 部分」的 move 在拥有所有权时无法两全。解法是 match 借用:match &opt { o @ Some(s) => .. },此时 o: &Option<String>,s: &String,皆大欢喜。这也是 as_ref 惯用法的另一动机。
⚠️ 注意:遇到「编译器抱怨值被 move 后仍被使用」(
use of partially moved value或use of moved value),多半是 match 消耗了所有权。第一反应应是:能否match &val或match val.as_ref(),用借用代替 move?这能解决大半问题。
实战示例
解析器:键值对与命令行参数
解析 key=value 形式的配置行,let-else + 模式匹配让代码扁平而清晰:
1 | |
命令行参数分发用 match 对子命令穷尽匹配:
1 | |
切片解构 [_, "add", title] 极其直观——「第一个是程序名,第二个是 add,第三个是标题」。这种写法比 if args[1] == "add" 既安全又表意。
状态机分发
用枚举建模状态机,match 做状态转移,穷尽性保证每条转移都被处理:
1 | |
新增状态或事件时,编译器会在每个 match 提醒你补全转移,杜绝「漏处理某条边」的状态机 bug。
AST 遍历
表达式 AST 的求值与化简,是嵌套解构的经典舞台:
1 | |
这里 match (&a, &b) 同时解构两个表达式的形状,Expr::Num(0.0) 字面量模式精确匹配零。模式匹配让「形状驱动的化简」表达得几乎像数学公式。
配置解构
把嵌套配置一次性解构成扁平字段,减少中间变量:
1 | |
let 解构 + .. 让你「按需取字段」,不必 cfg.db.host 一长串。配合 match ergonomics,cfg 是引用时 host/port 自动是 &String/&u16。
最佳实践与速查表
何时用哪种构造:
- 全部分支都要处理、变体可能增加 →
match(享穷尽性); - 只关心一种变体、变体稳定不变 →
if let(轻量); - 逐项消费迭代器/栈 →
while let; - 前置校验、失败即返回 →
let-else(happy path 不缩进)。
模式编写要点:
- 自家枚举显式列变体,慎用
_(会吞掉新增变体); - 默认分支用
unreachable!()/panic!()带说明,优于静默_ => {}; - 解构复合类型用
..忽略不关心的字段; - 匹配引用优先靠 match ergonomics,必要时
as_ref/as_mut借用; - 既测试范围又保留值用
@; - 守卫保持纯判断,无副作用、不假设穷尽。
所有权协作要点:
match拥有所有权的值会 move 内部数据,需保留时用match &val;Option/Result持非 Copy 数据时,as_ref/as_mut/as_deref是惯用借用;_不 move,变量会 move——忽略值时用_。
模式能力速查表:
| 模式 | 语法 | 典型用途 |
|---|---|---|
| 字面量 | 1、'a'、"x" | 按值相等 |
| 变量绑定 | x | 绑定任意值 |
| 通配符 | _ | 忽略,不 move |
| 或模式 | a | b | c | 多值合并 |
| 范围 | 0..=9、'a'..='z' | 区间匹配 |
| 结构体 | S { x, y } | 字段解构 |
| 元组 | (a, b, ..) | 位置解构 |
| 枚举 | E::V(x) | 变体解构 |
| 切片 | [a, .., z] | 序列首尾 |
| 引用 | &x、&mut x | 解引用 |
@ 绑定 | n @ 0..=9 | 测试 + 绑定 |
ref/ref mut | ref x | 显式借用 |
| 忽略剩余 | .. | 跳过其余字段 |
可反驳性速查:
| 位置 | 接受可反驳? |
|---|---|
let、函数参数、for、闭包参数 | 否(仅不可反驳) |
match、if let、while let、let-else | 是 |
🔬 进阶:模式匹配是 Rust 类型系统的「解构面」——它和 trait、泛型共同构成了 Rust 的多态能力。trait 提供运行时/编译时多态(见《类型系统》),而模式匹配提供「按形状分支」的多态。二者常配合使用:一个返回枚举的 trait 方法,调用方用
match解构结果。掌握模式匹配,你就握住了 Rust 表达力的另一半。
下一章《类型系统:泛型、trait 与多态》介绍 Rust 如何用泛型与 trait 在编译期实现多态,以及它们与模式匹配如何协作。



















