模式匹配
问题
Rust 的模式匹配有哪些使用方式?为什么说它比 switch/case 强大得多?
答案
模式匹配是 Rust 最强大的控制流特性之一。match 表达式不仅能匹配值,还能解构复杂数据结构,并且编译器强制穷尽所有可能。
match 表达式
fn describe_number(n: i32) -> &'static str {
match n {
1 => "一",
2 => "二",
3..=9 => "三到九", // 范围模式
10 | 20 => "十或二十", // 多值模式
n if n < 0 => "负数", // 匹配守卫(guard)
_ => "其他", // 通配符,匹配所有剩余
}
}
穷尽检查
match 必须覆盖所有可能的值,遗漏任何情况都会导致编译错误。这是 Rust 类型安全的重要保障——不会出现"忘了处理某种情况"的 bug。
解构枚举
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle { base: f64, height: f64 },
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle { base, height } => base * height / 2.0,
}
}
解构结构体
struct Point {
x: i32,
y: i32,
}
fn classify_point(point: &Point) -> &str {
match point {
Point { x: 0, y: 0 } => "原点",
Point { x, y: 0 } => "x 轴上",
Point { x: 0, y } => "y 轴上",
Point { x, y } if *x == *y => "对角线上",
_ => "一般位置",
}
}
解构元组与数组
fn main() {
// 元组解构
let point = (3, -5);
match point {
(0, 0) => println!("原点"),
(x, 0) => println!("x 轴, x = {}", x),
(0, y) => println!("y 轴, y = {}", y),
(x, y) => println!("({}, {})", x, y),
}
// 数组/切片解构
let numbers = [1, 2, 3, 4, 5];
match &numbers[..] {
[] => println!("空"),
[single] => println!("单个: {}", single),
[first, .., last] => println!("首: {}, 尾: {}", first, last),
}
}
if let 与 while let
只关心一种匹配时,用 if let 简化代码:
fn main() {
let config_max: Option<u8> = Some(3);
// match 写法
match config_max {
Some(max) => println!("max = {}", max),
_ => (),
}
// if let 简写(等价于上面)
if let Some(max) = config_max {
println!("max = {}", max);
}
// if let else
if let Some(max) = config_max {
println!("max = {}", max);
} else {
println!("无配置");
}
// while let:循环匹配
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{}", top); // 3, 2, 1
}
}
let else(Rust 1.65+)
let else 在匹配失败时执行发散操作(return、panic 等):
fn get_count(input: &str) -> Result<u32, String> {
// 匹配成功:绑定 count_str
// 匹配失败:执行 else 分支(必须发散)
let Some(count_str) = input.strip_prefix("count=") else {
return Err("格式错误".to_string());
};
let count: u32 = count_str.parse().map_err(|e| format!("解析错误: {}", e))?;
Ok(count)
}
模式语法大全
| 模式 | 示例 | 说明 |
|---|---|---|
| 字面量 | 1, "hello", true | 匹配具体值 |
| 变量绑定 | x, name | 绑定匹配的值到变量 |
| 通配符 | _ | 匹配任何值,不绑定 |
| 范围 | 1..=5, 'a'..='z' | 闭区间范围 |
| 多值 | 1 | 2 | 3 | 匹配多个值之一 |
| 解构元组 | (x, y) | 解构元组 |
| 解构结构体 | Point { x, y } | 解构命名字段 |
| 解构枚举 | Some(v) | 解构枚举变体 |
| 忽略字段 | Point { x, .. } | .. 忽略剩余字段 |
| 引用 | &val | 解构引用 |
| 匹配守卫 | x if x > 0 | 额外条件判断 |
@ 绑定 | id @ 3..=7 | 匹配并同时绑定值 |
| 切片模式 | [first, .., last] | 解构切片 |
@ 绑定
在匹配模式的同时将值绑定到变量:
fn classify(n: i32) -> &'static str {
match n {
n @ 1..=12 => {
println!("月份: {}", n);
"有效月份"
}
n @ 13.. => {
println!("{} 不是有效月份", n);
"无效"
}
_ => "非正数",
}
}
模式匹配与所有权
模式匹配时需要注意所有权规则:
fn main() {
let msg = Some(String::from("hello"));
// match 会 move 值(如果不是引用)
match msg {
Some(s) => println!("{}", s), // s 获得 String 的所有权
None => println!("none"),
}
// println!("{:?}", msg); // ❌ msg 已被 move
// 解决方案 1:match 引用
let msg = Some(String::from("hello"));
match &msg {
Some(s) => println!("{}", s), // s 是 &&String
None => println!("none"),
}
println!("{:?}", msg); // ✅ msg 未被 move
// 解决方案 2:使用 ref 关键字
let msg = Some(String::from("hello"));
match msg {
Some(ref s) => println!("{}", s), // ref 创建引用而非 move
None => println!("none"),
}
println!("{:?}", msg); // ✅
}
常见面试问题
Q1: match 和其他语言的 switch 有什么区别?
答案:
| 特性 | Rust match | C/Java switch |
|---|---|---|
| 穷尽检查 | 编译器强制 | 不强制 |
| Fall-through | 无(每个分支独立) | 默认 fall-through |
| 数据解构 | 支持 | 不支持 |
| 匹配类型 | 任意模式 | 整数/字符串 |
| 返回值 | 是表达式,有返回值 | 语句,无返回值 |
| 范围匹配 | 1..=5 | 不直接支持 |
| 守卫条件 | if 守卫 | 不支持 |
Q2: if let 和 match 的区别是什么?什么时候用哪个?
答案:
match:穷尽匹配,必须处理所有情况。适合多分支场景if let:只关心一种模式,其余忽略或统一处理。适合只有 1-2 个分支的场景
// 只关心 Some → 用 if let
if let Some(value) = optional {
use_value(value);
}
// 3+ 种情况 → 用 match
match result {
Ok(data) => process(data),
Err(IoError(e)) => handle_io(e),
Err(ParseError(e)) => handle_parse(e),
}
let else 适合"不匹配就提前返回"的 early return 模式。
Q3: 什么是"可反驳"和"不可反驳"模式?
答案:
- 不可反驳(irrefutable)模式:能匹配任何传入的值,不会失败。例如
let x = 5;中的x - 可反驳(refutable)模式:可能匹配失败。例如
Some(x)在值为None时失败
使用规则:
let、函数参数、for循环只能使用不可反驳模式if let、while let接受可反驳模式(失败时走 else 或结束循环)match的每个分支是可反驳的,但所有分支组合必须覆盖所有可能(整体不可反驳)
// ❌ let 不能用可反驳模式
// let Some(x) = some_option;
// ✅ if let 可以
if let Some(x) = some_option { ... }
Q4: ref 和 & 在模式匹配中有什么区别?
答案:
&:解构引用,获取引用指向的值ref:创建引用,将值"借出"而非 move
let value = Some(String::from("hello"));
// ref:在 match 时创建引用(不 move)
match value {
Some(ref s) => println!("{}", s), // s: &String
None => {}
}
// value 仍有效
// &:解构引用
let reference = &42;
match reference {
&val => println!("{}", val), // val: i32(从 &i32 解构出 i32)
}
现在推荐的做法是 match &value 而非 match value { Some(ref s) => ... },两者效果相同但前者更简洁。
Q5: 匹配守卫(match guard)会影响穷尽检查吗?
答案:
是的,匹配守卫不被编译器用于穷尽性检查。即使逻辑上守卫已覆盖所有情况,仍需要 _ 兜底:
let n = 5;
match n {
x if x > 0 => println!("正数"),
x if x < 0 => println!("负数"),
x if x == 0 => println!("零"),
_ => unreachable!(), // 编译器要求兜底
}
原因:编译器只做模式层面的穷尽检查,不分析守卫条件的逻辑。
Q6: 如何在模式匹配中忽略部分值?
答案:
// _ 忽略单个值
let (_, y) = (1, 2);
// .. 忽略剩余字段/元素
struct Config { a: i32, b: i32, c: i32, d: i32 }
let Config { a, .. } = config;
// _变量名 忽略但不报 unused 警告
let _unused = compute();
// 嵌套忽略
match some_tuple {
(first, _, third, ..) => println!("{}, {}", first, third),
}