模式与模式匹配
模式(pattern) 是 Rust 中一种特殊的语法,用来匹配类型的结构,无论类型简单还是复杂.将模式与 match 表达式及其他结构结合使用,可以让你对程序的控制流有更多掌控.模式由以下内容中的某种组合构成:
- 字面值
- 解构后的数组、枚举、结构体或元组
- 变量
- 通配符
- 占位符
模式的一些例子包括 x、(a, 3) 和 Some(Color::Red).在允许使用模式的上下文中,这些组成部分描述了数据的形状.程序随后会让值与模式匹配,以决定它是否具有继续运行某段特定代码所需的数据形状.
所有可以使用模式的位置
在 match 模式匹配 章节中,我们已经详细介绍了几个模式匹配的例子.
match 分支
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}match 表达式有一个要求,那就是它必须是穷尽的(exhaustive):match 表达式中值的所有可能情况都必须被覆盖到.确保覆盖所有可能性的一种方式,是让最后一个分支使用"捕获所有"的模式;例如,一个可以匹配任意值的变量名永远不会失败,因此它能够覆盖所有剩余情况.
特定的模式 _ 可以匹配任何东西,但它永远不会绑定到变量上,因此常被用于最后一个 match 分支.当你想忽略某个未指定的值时,_ 模式会非常有用.稍后在本章的"忽略模式中的值"一节中,我们会更详细地讨论 _ 模式.
let 语句
没错,变量赋值也是一种模式匹配!
let PATTERN = EXPRESSION;Rust 会拿表达式与模式进行比较,并将它找到的任何名字赋值.
为了更清楚地看出 let 的模式匹配这一面,来看下面的示例——它在 let 中使用模式来解构一个元组:
// ✅ 成功匹配,x=1, y=2, z=3
let (x, y, z) = (1, 2, 3);
// ❌ 失败:模式 (x, y) 期望两个元素的元组,但提供了三个元素
let (x, y) = (1, 2, 3); 条件 if let 表达式
主要用来简写只匹配一种情况的 match 表达式:
if let PATTERN = EXPRESSION {
// 满足条件时执行的代码
} else {
// 不满足条件时执行的代码
}也可以混合使用 if let、else if、else if let 和 else:
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}while let 条件循环
与 if let 在结构上类似的是 while let 条件循环,它允许 while 循环在模式持续匹配期间一直运行:
while let PATTERN = EXPRESSION {
// 满足条件时执行的代码
}for 循环
在 for 循环中,紧跟在 for 关键字后面的值就是一个模式.例如,在 for x in y 中,x 就是那个模式:
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("第 {index} 个元素是 {value}");
}在
for循环中使用模式来解构元组
enumerate()方法返回一个迭代器,生成一个包含元素索引和值的元组.
函数参数
函数参数也可以是模式,闭包参数同样可以是模式:
// 函数参数
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
// 闭包
let point = (3, 5);
let print_coordinates = |&(x, y): &(i32, i32)| {
println!("Current location: ({x}, {y})");
};
print_coordinates(&point);可反驳性
| 类型 | 说明 |
|---|---|
| 不可反驳模式 | 必须匹配,对传入的任意可能值都能匹配的模式 |
| 可反驳模式 | 可能匹配,对某些可能值会匹配失败的模式 |
函数参数、let语句和for循环只能接受不可反驳模式,因为当值不匹配时,程序无法做出有意义的处理.if let、while let表达式以及let...else语句既接受可反驳模式,也接受不可反驳模式.
// ❌ 错误:Some(x) 是可反驳模式,若 some_option_value 为 None 将导致编译错误
let Some(x) = some_option_value; 修改为:
// ✅ 正确:使用 let...else 语句处理可反驳模式
let Some(x) = some_option_value else {
return; // 如果 some_option_value 是 None,提前返回
};在 if let 中使用不可反驳模式没有意义
// ⚠️ 警告:warning: irrefutable `let...else` pattern
if let x = 5 {
println!("{x}");
} 模式匹配语法
匹配字面值
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"), // 匹配所有其他值
}匹配命名变量
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {y}"), // 这里的 y 是新变量,遮蔽了外部的 y
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
// 输出:
// Matched, y = 5
// at the end: x = Some(5), y = 10注意:
match分支中的y是新建变量,与外部y无关.
匹配多个模式
使用 | 语法匹配多个模式,它代表或(or)运算符:
let x = 1;
match x {
1 | 2 => println!("one or two"), // x 是 1 或 2 时都会执行
3 => println!("three"),
_ => println!("anything"),
}通过 ..= 匹配值范围
let x = 5;
match x {
1..=5 => println!("one through five"), // x 是 1、2、3、4 或 5 时执行
_ => println!("something else"),
}编译器会在编译时检查范围不为空,而 char 和数字值是 Rust 仅有的可以判断范围是否为空的类型,所以范围只允许用于数字或 char 值.
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
// 输出:early ASCII letter解构并分解值
结构体
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
// 解构 Point,将 x 绑定到 a,y 绑定到 b
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}解构结构体字段为单独的变量
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
// 使用字段简写语法解构
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}使用结构体字段简写解构
fn main() {
let p = Point { x: 0, y: 7 };
match p {
// 匹配 y 为 0 的情况,并将 x 的值绑定到变量 x
Point { x, y: 0 } => println!("On the x axis at {x}"),
// 匹配 x 为 0 的情况,并将 y 的值绑定到变量 y
Point { x: 0, y } => println!("On the y axis at {y}"),
// 匹配不在 x 轴或 y 轴上的点,并将 x 和 y 的值绑定到变量 x 和 y
Point { x, y } => println!("On neither axis: ({x}, {y})"),
}
}
match将Point值分成三种情况:位于 x 轴、y 轴或不在任何轴上
枚举
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
// 匹配 Message::Quit 变体,没有数据需要解构
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
// 匹配 Message::Move 结构体变体,并解构其 x 和 y 字段
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
// 匹配 Message::Write 元组变体,并将其中的 String 数据绑定到变量 text
Message::Write(text) => {
println!("Text message: {text}");
}
// 匹配 Message::ChangeColor 元组变体,并将其中的三个 i32 数据绑定到变量 r、g 和 b
Message::ChangeColor(r, g, b) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
}
}
// 输出:Change color to red 0, green 160, and blue 255嵌套的结构体和枚举
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
// 匹配 Message::ChangeColor 变体,并进一步匹配其中的 Color 枚举的 Rgb 变体,解构出 r、g 和 b 的值
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
// 匹配 Message::ChangeColor 变体,并进一步匹配其中的 Color 枚举的 Hsv 变体,解构出 h、s 和 v 的值
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}");
}
// 匹配 其他 变体,没有数据需要解构
_ => (),
}
}结构体和元组
可以用复杂的方式来混合、匹配和嵌套解构模式.
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });忽略模式中的值
用 _ 忽略整个值
_ 可作为通配符用于任意模式,包括函数参数:
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {y}");
}
fn main() {
foo(3, 4);
}用嵌套的 _ 忽略部分值
当不需要 Some 中的值时,在模式内使用 _ 匹配 Some 变体
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {setting_value:?}");忽略元组的多个部分
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}");
}
}通过在变量名开头加 _ 来忽略未使用变量
如果你创建了一个变量却不在任何地方使用它,Rust 通常会给你一个警告.为此可以用下划线作为变量名的开头来消除警告:
fn main() {
let _x = 5; // 不会产生未使用变量警告
let y = 10; // ⚠️ 会产生未使用变量警告
}注意:
_x仍会将值绑定到变量(所有权会转移),而单独的_则完全不会绑定.
let s = Some(String::from("Hello!"));
// _s 仍会获取 s 的所有权,导致后续无法使用 s
if let Some(_s) = s {
// 使用 _ 不会绑定值,s 的所有权不会转移
if let Some(_) = s {
println!("found a string");
}
println!("{s:?}");用 .. 忽略值的剩余部分
对于有多个部分的值,可以使用 .. 语法只使用特定部分并忽略其余值:
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {x}"), // 忽略 y 和 z
}使用
..忽略Point中除x以外的字段
.. 会自动扩展为所需数量的忽略项:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}只匹配元组中的第一个和最后一个值,忽略其余所有值
使用 .. 必须是无歧义的,否则 Rust 会报错:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
// ❌ error: `..` can only be used once per tuple pattern
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}Rust 无法确定
second前后各应忽略多少个值,因此编译报错
使用匹配守卫添加额外条件
匹配守卫(match guard) 是指定于 match 分支模式之后的额外 if 条件,必须同时满足才能选择该分支.匹配守卫可以表达比单独模式更复杂的逻辑.
注意:匹配守卫仅在
match表达式中可用,不能用于if let或while let.
let num = Some(4);
match num {
// 匹配 Some 变体,并且当其中的值满足 x % 2 == 0 时才选择这个分支
Some(x) if x % 2 == 0 => println!("The number {x} is even"),
Some(x) => println!("The number {x} is odd"),
None => (),
}使用匹配守卫来测试与外部变量的相等性
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
// 使用匹配守卫测试与外部变量 y 的相等性(避免变量遮蔽问题)
Some(n) if n == y => println!("Matched, n = {n}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}| 与匹配守卫结合时,守卫作用于整个模式组合
let x = 4;
let y = false;
match x {
// 匹配 4、5 或 6,并且当 y 为 true 时才选择这个分支
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
// 输出:no使用 @ 绑定
@ 运算符允许我们在创建一个存放值的变量的同时测试其值是否匹配模式:
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7, // 匹配范围的同时将值绑定到 id_variable
} => {
println!("Found an id in range: {id_variable}")
}
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range") // 匹配范围但不绑定变量
}
Message::Hello { id } => println!("Found some other id: {id}"),
}一个更简单的例子:
let x = 5;
match x {
n @ 1..=10 => println!("1到10之间的值: {n}"),
n @ 11..=20 => println!("11到20之间的值: {n}"),
_ => println!("不在范围内"),
}