跳到主要内容

模式匹配

问题

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 matchC/Java switch
穷尽检查编译器强制不强制
Fall-through无(每个分支独立)默认 fall-through
数据解构支持不支持
匹配类型任意模式整数/字符串
返回值是表达式,有返回值语句,无返回值
范围匹配1..=5不直接支持
守卫条件if 守卫不支持

Q2: if letmatch 的区别是什么?什么时候用哪个?

答案

  • 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 letwhile 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),
}

相关链接