Skip to content

模式与模式匹配

在前面的章节中,我们已经多次使用了 matchif let 来处理枚举类型的不同变体,这些都是 Rust 中模式匹配(pattern matching)的应用。模式匹配是 Rust 的核心特性之一,允许我们以一种简洁、强大且安全的方式检查和解构复杂数据结构。

模式(pattern) 是 Rust 中一种特殊语法,用来与某个值的结构进行匹配,无论类型简单还是复杂。模式由以下部分的某种组合构成: 字面值、解构后的数组/枚举/结构体/元组、变量、通配符、占位符。

Rust 的模式匹配具有以下核心特性:

  • 穷尽性: match 要求覆盖被匹配值的所有可能情况,否则编译报错,保证运行时安全
  • 表达式性质: match 是一个表达式,可以有返回值,支持直接赋值给变量
  • 按序匹配: 从第一个分支开始逐一检查,执行第一个匹配成功的分支,之后不再继续
  • 所有权规则: 匹配默认转移被匹配值的所有权,可用 & 避免转移

模式可以出现在六种位置,不同位置对可反驳性的要求不同:

位置形式接受的模式类型
match 分支match x { PATTERN => expr }可反驳(末尾 _ 不可反驳)
let 语句let PATTERN = expr仅不可反驳
if let 表达式if let PATTERN = expr { }可反驳和不可反驳
while let 循环while let PATTERN = expr { }可反驳和不可反驳
for 循环for PATTERN in iter { }仅不可反驳
函数参数fn f(PATTERN: Type)仅不可反驳

模式可以出现的位置

match 分支

match 将值与多个分支的模式逐一比较,执行第一个匹配成功的分支。基本语法如下:

rust
match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    _ => EXPRESSION,   // 通配符,匹配所有剩余情况
}

match从上到下依次检查分支,一旦匹配成功即停止。match 要求穷尽所有可能情况(exhaustive),可以用 other(绑定值)或 _(丢弃值)作为最后一个分支来覆盖剩余情况:

rust
#[derive(Debug)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

let dir = Direction::Down;

match dir {
    Direction::Up    => println!("向上"),
    Direction::Down  => println!("向下"),
    other            => println!("其他方向: {:?}", other),  // 绑定值,可使用
    // _             => println!("其他"),                   // 丢弃值,不使用内容
}

match 作为表达式: match 可以返回值并赋值给变量,所有分支必须返回相同类型:

rust
let x = (11, 22);

// 赋值给变量: 所有分支必须返回相同类型(此处为 i32)
let y = match x {
    (a, 11) => {
        println!("{}", a);
        a         // 返回 i32
    },
    (a, b) => {
        println!("{}, {}", a, b);
        a + b     // 返回 i32
    },
};

// 作为独立表达式(不赋值给变量): 分支返回 () 即可
match x {
    (a, b) => println!("{}, {}", a, b),  // 隐式返回 ()
}

let 语句

变量绑定本质上也是一种模式匹配!

let PATTERN = EXPRESSION 将右侧表达式与左侧模式进行匹配,并将找到的变量名逐一赋值:

rust
// 最简单的模式: 变量名
let x = 5;                  // 将 5 匹配到模式 x

// 元组解构: 将 (1, 2, 3) 与模式 (x, y, z) 匹配
let (x, y, z) = (1, 2, 3);  // ✅ x=1, y=2, z=3

// ❌ 模式元素数量不匹配
let (x, y) = (1, 2, 3); 

if let / let-else

if let 是只关心一种匹配情况时 match 的简写形式。如果匹配成功就执行代码块,否则跳过(或执行 else 块):

rust
// 只关心 Some 变体
if let Some(x) = some_value {
    println!("有值: {}", x);
} else {
    println!("没有值");
}

if let 可以与 else ifelse if letelse 自由组合,形成多条件分支:

rust
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();

if let Some(color) = favorite_color {
    println!("使用最爱的颜色: {}", color);
} else if is_tuesday {
    println!("周二用绿色!");
} else if let Ok(age) = age {
    if age > 30 {
        println!("用紫色");
    } else {
        println!("用橙色");
    }
} else {
    println!("用蓝色");
}

注意: 与 match 不同,编译器不检查 if let 分支之间是否穷尽或有逻辑关联,即使遗漏某种情况也不会编译报错。

if let 链式条件: 用 && 将多个 let 解包和布尔条件串联在同一个 if let 中:

rust
// 同时解包多个 Option
if let Some(a) = opt1 && let Some(b) = opt2 {
    println!("{a}, {b}");  // a、b 均已绑定
}

// 解包并附加条件
if let Some(x) = opt && x > 0 {
    println!("{x}");
}

while let 同样支持 && 链式条件:

rust
while let Some(x) = iter.next() && x > 0 {
    println!("{x}");
}

let-else(守卫解构): 如果模式匹配,则必须在 else 块中用 returnbreakpanic! 等中止当前流程;如果匹配成功,绑定的变量直接作用于当前作用域(而非 if 块内):

rust
let San::jump(distance) = san else {
    panic!("不是跳跃状态");
};
println!("跳了 {} 米", distance);  // distance 在当前作用域中直接可用

// 在函数中配合 return 使用
fn check_jump(san: San) -> i32 {
    let San::jump(d) = san else {
        return 0;  // 不是跳跃状态,返回 0
    };
    d
}

if let 表达"如果匹配则做某事",let-else 表达"必须匹配,否则退出",语义更明确。else 块中必须改变控制流,不能继续执行后续代码。

while let 条件循环

只要模式持续匹配,while let 就一直执行循环体:

rust
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);

// stack.pop() 有元素时返回 Some,无元素时返回 None
while let Some(top) = stack.pop() {
    println!("{}", top);  // 依次输出 3, 2, 1
}
// pop() 返回 None 时,匹配失败,循环退出

for 循环

for 循环中 in 之前的控制变量就是一个模式,可以直接用来解构:

rust
let v = vec!['a', 'b', 'c'];

// (index, value) 是模式,解构 enumerate() 返回的元组
for (index, value) in v.iter().enumerate() {
    println!("第 {} 个元素是 {}", index, value);
}

函数参数

函数参数也是模式,可以直接在参数位置进行解构(闭包参数同理):

rust
// 参数 &(x, y) 是模式,直接解构引用元组
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("当前坐标: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);  // x=3, y=5
}

// 闭包参数同样支持模式解构
let print_point = |&(x, y): &(i32, i32)| {
    println!("({}, {})", x, y);
};
print_point(&point);

可反驳性

类型说明
不可反驳模式(irrefutable)对任意可能值都能匹配成功,必须匹配,否则编译报错
可反驳模式(refutable)对某些值可能匹配失败,失败时不执行对应代码

let 变量赋值、for 循环、函数传参只接受不可反驳模式if letwhile letlet-else 接受可反驳模式match 两种都支持: 明确指定的分支是可反驳的,末尾的 _ 或变量名是不可反驳的。

rust
// ❌ let 中使用可反驳模式: 若为 None 则无法匹配,编译失败
let Some(x) = some_option_value; 
rust
// ✅ 改用 let-else 处理可反驳模式
let Some(x) = some_option_value else {
    return;
};
rust
// ⚠️ if let 中使用不可反驳模式: 一定会成功,没有意义,产生警告
if let x = 5 { 
    println!("{x}");
}

完整模式语法

匹配字面值

直接用字面量作为模式:

rust
let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

匹配命名变量

模式中的变量名会遮蔽(shadow)外部同名变量,成为一个全新的局部绑定:

rust
let x = Some(5);
let y = 10;

match x {
    Some(50) => println!("Got 50"),
    Some(y)  => println!("匹配到: y = {y}"),  // 此 y 是新变量,值为 5,遮蔽外部 y
    _        => println!("默认: x = {x:?}"),
}

println!("x = {x:?}, y = {y}");
// 输出:
// 匹配到: y = 5
// x = Some(5), y = 10

分支内绑定的变量只在该分支作用域内有效,分支结束后外部变量恢复。

多选一模式 |

使用 | 匹配多个模式,表示"或":

rust
let x = 1;

match x {
    1 | 2 => println!("one or two"),
    3     => println!("three"),
    _     => println!("anything"),
}

范围匹配 ..= / ..

匹配连续范围,支持闭合范围 ..=半开范围 ..,适用于数字和字符类型。推荐使用 ..=

rust
// 数值范围
let x = 79;
match x {
    0..=59   => println!("不及格"),
    60..=89  => println!("良好"),
    90..=100 => println!("优秀"),
    _        => println!("超出范围"),
}

// 字符范围
let c = 'c';
match c {
    'a'..='j' => println!("前段字母"),
    'k'..='z' => println!("后段字母"),
    _         => (),
}

当两个范围首尾相邻但是却不连续时(如 60..89 紧接 89..=100,不包含 89),编译器会认为你可能写错了,发出 non_contiguous_range_endpoints 警告。

解构赋值

模式匹配可以将复合类型拆解为独立变量,称为解构(destructuring)

结构体

rust
struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 0, y: 7 };

// 字段名与变量名相同时可简写(等价于 Point { x: x, y: y })
let Point { x, y } = p;

// 重命名: 将 x 绑定到 a,y 绑定到 b
let Point { x: a, y: b } = p;

// 在 match 中解构,支持字面值约束
match p {
    Point { x, y: 0 } => println!("在 x 轴上: x = {x}"),
    Point { x: 0, y } => println!("在 y 轴上: y = {y}"),
    Point { x, y }    => println!("不在轴上: ({x}, {y})"),
}

枚举

rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

let msg = Message::ChangeColor(0, 160, 255);

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})"),
}
// 输出: 颜色: (0, 160, 255)

嵌套的结构体和枚举

rust
enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    ChangeColor(Color),
    // ...其他变体
}

let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

match msg {
    Message::ChangeColor(Color::Rgb(r, g, b)) => println!("RGB: {r}, {g}, {b}"),
    Message::ChangeColor(Color::Hsv(h, s, v)) => println!("HSV: {h}, {s}, {v}"),
    _ => (),
}

元组与混合解构

rust
// 解构元组
let (a, b, c) = (1, 2, 3);

// 混合解构: 同时解构元组和结构体
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });

忽略模式中的值

_ 忽略整个值

_ 匹配任何值但不绑定,编译器会忽略它,可用于函数参数或 match 的通配分支:

rust
fn foo(_: i32, y: i32) {
    println!("只用 y: {y}");
}

用嵌套的 _ 忽略部分值

rust
// 忽略 Some 内部的值
match (setting_value, new_setting_value) {
    (Some(_), Some(_)) => println!("已有值,不覆盖"),
    _ => { setting_value = new_setting_value; }
}

// 忽略元组中的部分元素
let numbers = (2, 4, 8, 16, 32);
match numbers {
    (first, _, third, _, fifth) => println!("{first}, {third}, {fifth}"),
}

_x 忽略未使用变量

变量名以 _ 开头可消除未使用警告,但注意 _x 仍会绑定值(会转移所有权),而单独的 _ 不绑定:

rust
fn main() {
    let _x = 5;  // 不产生未使用警告
    let y = 10;  // ⚠️ 会产生未使用变量警告
}
rust
let s = Some(String::from("Hello!"));

if let Some(_s) = s {  // _s 获取了 s 的所有权,后续 s 不可用
if let Some(_) = s {   // _ 不绑定,s 所有权不转移
    println!("found a string");
}

println!("{s:?}");

.. 忽略值的剩余部分

.. 自动扩展为所需数量的忽略项,但使用时不能产生歧义:

rust
struct Point { x: i32, y: i32, z: i32 }

let origin = Point { x: 0, y: 0, z: 0 };

match origin {
    Point { x, .. } => println!("x = {x}"),  // 忽略 y 和 z
}

// 元组: 只取首尾
let numbers = (2, 4, 8, 16, 32);
match numbers {
    (first, .., last) => println!("头 {first},尾 {last}"),
}

// ❌ 有歧义: Rust 无法确定 second 前后各忽略多少个
match numbers {
    (.., second, ..) => println!("{second}"), 
}

匹配守卫(match guard)

match 分支模式后面可以加 if 条件作为额外约束,模式和守卫都满足才会选择该分支:

rust
let num = Some(4);

match num {
    Some(x) if x % 2 == 0 => println!("{x} 是偶数"),
    Some(x)                => println!("{x} 是奇数"),
    None                   => (),
}

守卫可以引用外部变量,避免变量遮蔽问题:

rust
let x = Some(5);
let y = 10;

match x {
    Some(50)          => println!("Got 50"),
    Some(n) if n == y => println!("n = {n}"),  // 与外部 y 比较,不遮蔽 y
    _                 => println!("默认: x = {x:?}"),
}

| 与守卫结合时,守卫作用于整个 | 组合(等价于给组合加括号):

rust
let x = 4;
let y = false;

match x {
    4 | 5 | 6 if y => println!("yes"),  // 等价于 (4 | 5 | 6) if y
    _              => println!("no"),
}
// 输出: no

匹配守卫语法(pattern if condition)仅在 match 中可用;在 if let / while let 中,可通过 Rust 2024 的 && 链式条件实现类似效果,例如 if let Some(x) = opt && x > 0 { ... }

@ 绑定

@ 允许在测试值是否匹配某个模式的同时,将该值绑定到一个变量:

rust
enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    Message::Hello { id: id @ 3..=7 }  => println!("id 在 3~7 范围内: {id}"),
    Message::Hello { id: 10..=12 }     => println!("id 在 10~12 范围内"),
    Message::Hello { id }              => println!("其他 id: {id}"),
}

简单示例:

rust
let x = 5;
match x {
    n @ 1..=10  => println!("1 到 10: {n}"),
    n @ 11..=20 => println!("11 到 20: {n}"),
    _           => println!("不在范围内"),
}

解构赋值与所有权

解构赋值时,非 Copy 类型的字段会发生所有权转移,导致原变量部分失效:

rust
struct Person {
    name: String,
    age: i32,
}

let p = Person { name: String::from("zhangsan"), age: 23 };

let Person { name, age } = p;  // name 从 p 中 Move 出来

println!("{}", name);   // ✅
println!("{}", age);    // ✅
// println!("{}", p.name); // ❌ name 字段所有权已转移

ref 借用而非转移

在模式的变量名前加 ref,表示借用该字段而非转移所有权(等价于对该字段取 &):

rust
let p = Person { name: String::from("zhangsan"), age: 23 };

// name 借用 p.name,所有权不转移
let Person { ref name, age } = p;

println!("{}", name);    // ✅
println!("{}", p.name);  // ✅ p.name 仍然有效
写法name 的类型原字段是否仍可用
let Person { name, .. } = pString(Move)
let Person { ref name, .. } = p&String(借用)

let ref x = value 等价于 let x = &value,均得到引用类型。

ref mut 可变借用

需要修改字段但不转移所有权时,使用 ref mut:

rust
let mut p = Person { name: String::from("zhangsan"), age: 23 };

match p {
    Person { ref mut name, age } => {
        name.push_str("_san");  // 可修改
        println!("name: {}, age: {}", name, age);
    }
}
println!("{:?}", p);  // ✅ p 仍然有效

也可以通过 match &mut p { ... } 对整个 match 的目标取可变引用,这样所有分支中无需手动加 ref mut,对有多个分支的 match 尤为方便。

对引用进行解构

解构一个引用时,绑定的变量也是对应元素的引用:

rust
let t = &(1, 2, 3);     // t 是引用

let (t0, t1, t2) = t;   // t0, t1, t2 的类型都是 &i32
let t0 = t.0;           // t0 是 i32(.0 自动解引用)
let t0 = &t.0;          // t0 是 &i32(对字段取引用)

// 用 & 在模式中匹配引用: 解构后得到值类型
let &(x, y, z) = t;     // x, y, z 都是 i32

for 循环迭代时同样适用:

rust
let v = vec![1, 2, 3];

for i in &v  { /* i 是 &i32,v 仍可用 */ }
for i in v   { /* i 是  i32,v 的所有权被消耗 */ }

基于 MIT 协议发布