match 模式匹配
本教程共 78 篇 · 第 30 篇 · 更新于 2026-08-08 · 约 9 分钟阅读
本节目标:用
match按”模式”把值分到不同分支处理,掌握字面值、范围、枚举变体解构的写法,并理解它”必须穷尽所有可能”的硬约束。
if 是按”条件真假”分支。但当你要处理”这个值到底是 A、B、C 哪一种”时,Rust 有个更强的工具:match 模式匹配。它把值和一系列”模式”比对,命中哪个就走哪条分支。枚举配 match,是 Rust 最经典的黄金组合。
很多刚从 C/Java 过来的同学会拿 match 和 switch 类比,但请先把 switch 的印象清空:switch 经常漏写 break 导致”贯穿”、又不强制覆盖所有情况,是 bug 温床。Rust 的 match 从设计上就堵死了这两点——分支不会自动穿透,而且所有情况必须被覆盖。这是”编译期就帮你查漏”的典范。
1-1 match 基本形状
fn main() {
let number = 3;
match number {
1 => println!("一"),
2 => println!("二"),
3 => println!("三"),
_ => println!("其他"),
}
}
match number { 模式 => 结果, ... }。程序从上往下拿 number 比对每个模式,命中 3 就执行对应分支。_ 是通配符,表示”其余所有没列的情况”。
Note每个分支用逗号
,分隔(最后一个可省)。分支可以是单行,也可以用=>后接花括号包多行。match 整体是表达式,可以有返回值。
1-2 必须穷尽:漏情况就编译报错
match 最严格也最安全的规定:所有可能的取值都必须被覆盖,否则编译不通过。
match number {
1 => println!("一"),
// 漏了 2、3 及其他 -> 编译报错!
}
编译器会红字提示”模式不穷尽(non-exhaustive)“。这保证了你不可能漏掉某种情况——很多语言里 switch 忘写 default 导致诡异行为,在 Rust 里根本编不过。最后一招用 _ 兜底,是满足”穷尽”的最常用手段。
Warning新手常因漏写
_ =>兜底而被 match 拒绝。记住:只要不能列出所有可能值(比如任意 i32),就必须用_收尾。枚举因为有固定变体数,可以逐个列全、不用_。
1-3 匹配范围与多值
一个分支能匹配多个值,用 | 或区间 ..=:
match number {
1 | 2 | 3 => println!("小数字"),
4..=9 => println!("中等数字"), // 4 到 9(含)
_ => println!("大数字或负数"),
}
1 | 2 | 3 表示三选一都进该分支;4..=9 是闭区间。这让”按区间分类”很简洁。
注意 ..= 是闭区间,右端点包含在内。如果想表达”不含右端点”的开区间,Rust 用的是 ..(比如 0..5),但它在 match 模式里不能直接当范围模式用,容易踩坑——match 里做区间匹配请认准 ..=。
1-4 分支可以绑定值
模式不仅能”比对”,还能”把命中的部分抓出来”用:
match number {
n @ 1..=5 => println!("在 1-5 内,具体是 {n}"),
n => println!("其他:{n}"),
}
@ 把命中的值绑到名字 n,分支里就能用。更常见的是匹配带数据的枚举变体时,把里面的数据解构出来(见下节)。@ 绑定在你”既想匹配一个范围、又想拿到具体值”时特别顺手——光写 1..=5 你只知道命中了范围,加上 n @ 才把具体数字搂住。
1-5 match 对付枚举:黄金组合
match 最大的舞台是枚举。配合上一章的 Message:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
fn process(msg: Message) {
match msg {
Message::Quit => println!("退出"),
Message::Move { x, y } => println!("移动到 ({x}, {y})"),
Message::Write(text) => println!("写:{text}"),
Message::ChangeColor(r, g, b) => println!("颜色 {r},{g},{b}"),
}
}
每个变体对应一个分支,并把里面的数据解构到变量(x, y、text、r, g, b)。编译器知道枚举有几个变体,所以这里列全四个就能穷尽,不需要 _。这种”按变体分流 + 顺手取数据”的写法,是 Rust 表达力的核心。
Note解构时变量名随便起(
x, y或a, b都行),它们只在对应分支内有效。结构体式变体{}用字段名解构,元组式变体()按位置解构。
1-6 match 处理 Option
上章说的 Option 几乎总是用 match 拆:
fn show(x: Option<i32>) {
match x {
Some(v) => println!("有值:{v}"),
None => println!("没值"),
}
}
Some(v) 模式把里面的值绑到 v,None 单独处理。因为 Option 只有两个变体,列全即穷尽。正是这套强制,让你”有值”路径绝不会意外碰到空。
1-7 match 作为表达式赋值
match 是表达式,能直接把结果赋给变量:
let desc = match number {
0 => "零",
1 => "一",
_ => "其他",
};
println!("{desc}");
每个分支末行(不加引号)的值,类型需一致,整体作为 match 的值赋给 desc。这延续了 Rust “表达式产出值”的一贯风格。
Tip当分支超过两三个、或要处理的是枚举的多种变体,优先用
match而非一长串if else。它既穷尽又清晰,编译器还能帮你查漏。
1-8 守卫与多值匹配
match 的分支还能加”守卫(guard)“——在模式后用 if 追加额外条件。比如匹配一个范围,但只想对其中偶数做特殊处理:
let n = 6;
match n {
x if x % 2 == 0 => println!("{x} 是偶数"),
x => println!("{x} 是奇数"),
}
x if x % 2 == 0 表示”先匹配任意值绑到 x,再要求 x 是偶数”。守卫让你在模式之外加更灵活的判断,分支内都能用绑定的 x。
同时匹配多个字面量用 |,匹配一个区间用 ..=:
match n {
1 | 2 | 3 => println!("小"),
4..=9 => println!("中"),
_ => println!("大"),
}
Warning守卫有个坑:带守卫的分支不算”穷尽保证”的来源——Rust 仍要求所有值能被某个顶层模式覆盖。
_兜底分支永远最稳妥,即使你觉得前面的守卫已覆盖全部情况,加了_编译器更省心,且你以后加新变体时不会突然编译失败。
Note
match必须穷尽,这是它和if let最大的区别。if let只关心一个模式、其余静默跳过;match强制你想清”所有情况怎么办”。当你”必须每种情况都处理”(比如协议里每种消息都不能漏)时,用match更安全。
1-9 字面量与绑定混用
match 的分支里,字面量模式和绑定可以混用。比如你想对特定值做特殊处理,其余”绑到一个变量”统一处理:
match value {
0 => println!("零"),
n => println!("非零:{n}"), // n 绑定了除 0 外的所有值
}
这里 0 是字面量模式(只匹配 0),n 是绑定模式(匹配其余一切、并把值绑到 n)。因为 n 覆盖了除 0 外所有情况,所以即使没写 _ 也满足穷尽。这种”特例单列、其余绑变量”的写法非常实用。
Note注意绑定模式必须放在字面量/范围模式之后——match 从上往下匹配,若把
n放最前面,后面的0就永远到不了。模式顺序很重要:具体的放前面,宽泛的(绑定、_)放后面。
1-10 一个常见坑:match 不是 switch
新手最容易把 match 当 switch 用,然后犯两个错。第一,以为分支会”贯穿”:Rust 的 match 命中一个分支就结束,不会掉进下一个,所以你不需要写 break,写了反而报错。第二,以为可以只写部分分支:Rust 强制穷尽,没覆盖全就编译失败,而不是像 switch 那样静默跳过。
还有人想用 match 匹配”浮点数相等”,比如 match x { 1.0 => ... }——这基本不可行,因为浮点数有精度问题,1.0 和 1.0000001 不相等。需要按范围或阈值判断时,老老实实用 if 或带守卫的区间,别指望用字面量去精确匹配浮点。
1-11 解构绑定与守卫的进阶
match 真正强大处在于”解构”:不光能匹配枚举变体,还能一边匹配一边把里面的数据拆出来命名。比如 match 一个带结构体数据的枚举变体时,可以 Msg::Move { x, y } => ... 直接把字段 x、y 绑到变量,在分支里直接用。配合元组、数组也一样:(a, b) => ... 把元组两个元素分别绑到 a、b。这让”按形状分流、随手取数”一气呵成,省去事后手动 .字段 取值。
Note匹配时还能加”守卫(guard)“:在模式后写
if 条件,只对”模式匹配且条件为真”的分支生效。例如Some(x) if x > 0 => ...表示”是 Some 且值大于 0 才走这枝”,其余Some落到别的支。守卫让你在匹配形状之外,再叠加更细的值判断,非常灵活。
还有 @ 绑定:当你想”既匹配一个范围、又把整个值绑到变量”时用得上,比如 n @ 1..=9 => ...,这样 n 拿到了实际值、又能用区间限定只走 1 到 9。模式匹配是 Rust 表达力的核心之一,初学先吃透字面量、|、..=、_ 和简单的绑定,解构、if 守卫、@ 这些等用到时再回头翻,不急。
1-12 match 在真实项目里怎么用
学完语法,最该想的是”什么时候该上 match”。经验上,凡是”一个值有几种互斥的状态、每种状态要跑不同逻辑”,match 就是首选。比如解析命令行参数:第一个参数是 init 就初始化、是 run 就运行、是 help 就打印帮助,用 match args.get(1).map(|s| s.as_str()) 配合字面值分支,比一长串 if 清爽得多,而且编译器会帮你确认”没漏哪种命令”。
Tip一个实用心法:当你写出
if x == A { ... } else if x == B { ... } else if x == C { ... }这种”链式相等判断”时,多半该换成 match。match 把”所有分支并列”这件事显式化,穷尽性检查还能防止你以后加了个新变体却忘了处理。
match 作为表达式赋值也极常用:很多函数开头就用 let config = match env { "dev" => ..., "prod" => ..., _ => ... }; 一次性算出一个值,后面整段逻辑都基于这个已确定的 config,不必到处判断环境。这种”先用 match 把不确定收拢成一个明确的值”的写法,能让后续代码大幅简化。
Note别把 match 写成超长巨无霸。如果一个 match 有十几二十个分支,说明你也许该换思路:用
if let处理少量特殊情况、用哈希表(map)存”键到处理函数”的映射、或把大 match 拆成几个小函数。match 强大,但超过一屏的分支会很难读。保持每个 match 聚焦、分支数可控,是可读性关键。
另外,match 和枚举配合时,给枚举变体起”有信息量的名字”很重要。比如错误类型用 Error::NotFound、Error::PermissionDenied 而不是笼统的 Error(1)、Error(2),match 分支读起来就像在念业务含义。这也是为什么 Rust 鼓励”用枚举把状态建模清楚”——match 只是顺理成章地把模型展开成逻辑。
1-13 嵌套 match 与多值同时匹配
match 还能嵌套,也支持”一次匹配多个值”。嵌套就是分支里再写 match,常见于”先按大类分、再按小类分”的场景,比如先 match 错误类型、再 match 具体错误码。多值同时匹配则用一个元组:match (a, b) { (0, 0) => ..., (0, _) => ..., (_, 0) => ..., _ => ... },一次看清两个变量的组合情况,比两层 if 清晰得多。
Note嵌套 match 容易让缩进变深。如果三层以上,考虑把内层抽成单独函数,或改用”先算出一个中间状态、再 match 一次”的扁平写法。可读性永远优先于”把逻辑塞进一个 match”。
1-14 回顾:match 与枚举为何是绝配
回看整章,match 真正的舞台是枚举。枚举把”一个值可能处于哪几种状态”建模清楚,match 则把”每种状态分别怎么办”写清楚,二者合起来,你几乎不用再写 if status == 1 这种脆弱的判断。而且编译器知道枚举有几种变体,match 漏写任何一种都会报错——这是”编译期就逼你处理所有情况”的安全网,是 Rust 最值得依赖的特性之一。
Tip写新功能时,先问”这里的状态能用枚举表达吗”。能的话,优先枚举 + match,而不是一堆布尔标志位或整数常量。前者让非法状态”无法被表示出来”,后者则要靠人肉记住每个数字的含义。前者是 Rust 推崇的”让类型系统替你挡错”的思路。
1-15 编译视角与自查清单
从编译器角度看,match 不只是语法糖。因为枚举的变体在编译期就确定、且互斥,编译器能把 match 编译成高效的”跳转表”或”连续比较”,性能往往比一长串 if/else 更好。更重要的是穷尽性检查:一旦你给枚举新增一个变体,所有没处理它的 match 都会立刻编译报错,把”漏处理”从运行时隐患变成编译期错误。这正是 Rust 所谓”改一处、编译器替你找出所有该改的地方”的威力。
Note这个特性在重构时价值巨大。比如你把
enum Status { Active, Inactive }改成多了个Pending,所有match status { Active => ..., Inactive => ... }都会变红,逼你逐个决定Pending该怎么处理。用整数常量或布尔位是绝对没有这种保护的——你只能靠人肉 review,迟早漏。
平时我给自己列了个小清单,照着做少踩坑:第一,分支体超过三行就抽函数,让 match 本体像目录;第二,写 match 时先看有没有”只关心一种情况”——有就用 if let 更轻;第三,别用 match 匹配浮点字面量,浮点精度问题会让匹配失灵,改用带守卫的范围判断;第四,给枚举变体起业务含义明确的名字,让分支读起来像文档。
Tip新手常担心”match 写起来长”。但它换来的是”不会漏、不会错、重构有保护”。前期多打几行,后期省下的是调试那些”只在某状态才崩”的诡异 bug 的时间。把 match 当成”把状态机显式写出来”的工具,心态会舒服很多。当你哪天给枚举加变体、看到编译器把所有漏处理的地方标红时,你会真心感激这个设计。
1-16 小结
match 按模式分支,必须穷尽(用 _ 兜底);支持字面量、| 多值、..= 区间、解构绑定与 @;可加 if 守卫叠加条件;还能作为表达式直接赋值,也支持嵌套与多值同时匹配。它和枚举、Option 是绝配。但有时你只想处理”某一种情况”、其他懒得写,写完整 match 嫌重——下一章 if let 就是为这种”只关心一个模式”的场景准备的简写。