Rust: 模式与匹配

0x01. 模式

模式是 Rust 中用于匹配复杂和简单类型的结构的特殊语法。将模式与匹配表达式和其他结构结合使用,可以更好地控制程序的控制流。模式由以下内容的组合组成:

  • Literals
  • Destructured arrays, enums, structs, or tuples
  • Variables
  • Wildcards
  • Placeholders

0x02. match 控制流

Rust 有一个非常强大的控制流构造,称为 match,它允许您将一个值与一系列模式进行比较,然后根据模式匹配执行代码。模式可以由文字值、变量名、通配符和许多其他内容组成;

1
2
3
4
5
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}

0x03. if let 简洁控制流

if let 语法让你可以将 iflet 组合成一种更简洁的方式,以处理匹配一个模式的值而忽略其余的值。

使用 match 编写的代码

1
2
3
4
5
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {max}"),
_ => (),
}

使用 if let 以更短的方式编写代码

1
2
3
4
5
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
}

使用 else 匹配其余情况

else 搭配的代码块与 match 表达式中与 _ 情况搭配的代码块相同,这相当于 if letelse

1
2
3
4
5
6
7
8
9
10
11
12
13
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {state:?}!"),
_ => count += 1,
}

let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {state:?}!");
} else {
count += 1;
}

综合例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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");
}
}

0x04. while let 条件循环

与 if let 结构类似,while let 条件循环允许 while 循环在模式持续匹配时一直运行。

1
2
3
4
5
6
7
8
9
let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
println!("{top}");
}

0x05. for 循环

在 for 循环中,关键字 for 后面的值是模式。例如,在 for x in y 中,x 就是模式。

1
2
3
4
5
let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}

我们使用枚举方法调整迭代器,以便它生成一个值和该值的索引,并将其放入元组中。生成的第一个值是元组 (0, ‘a’)。当此值与模式 (index, value) 匹配时,index 将为 0,value 将为 ‘a’,打印输出的第一行。

0x06. let 语句

实际上,我们也在其他地方使用过模式,包括在 let 语句中。例如,考虑使用 let 进行以下简单的变量赋值:

1
let x = 5;

每次使用这样的 let 语句时,您都在使用模式,尽管您可能没有意识到这一点!更正式地说,let 语句如下所示:

1
let PATTERN = EXPRESSION;

在 let x = 5; 这样的语句中,如果 PATTERN 位置有一个变量名,那么变量名只是一种特别简单的模式形式。Rust 将表达式与模式进行比较,并为其找到的任何名称赋值。因此,在 let x = 5; 示例中,x 是一个模式,其含义是“将此处匹配的内容绑定到变量 x”。因为名称 x 是整个模式,所以这个模式实际上意味着“将所有内容绑定到变量 x,无论其值是什么。”

为了更清楚地了解 let 的模式匹配方面,请考虑示例 18-4,它使用带有 let 的模式来解构元组。

1
let (x, y, z) = (1, 2, 3);

在这里,我们将一个元组与一个模式进行匹配。Rust 将值 (1, 2, 3) 与模式 (x, y, z) 进行比较,发现值与模式匹配,因此 Rust 将 1 绑定到 x,将 2 绑定到 y,将 3 绑定到 z。您可以将这个元组模式视为在其中嵌套了三个单独的变量模式。

错误示例:
错误地构造一个模式,其变量与元组中元素的数量不匹配。

1
let (x, y) = (1, 2, 3);

0x06. 函数参数

函数参数也可以是模式,还可以在闭包参数列表中以与函数参数列表中相同的方式使用模式。

1
2
3
4
5
6
7
8
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}

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

0x07. 可反驳性:模式是否可能无法匹配

模式有两种形式:可反驳的和不可反驳的。

  • 与任何可能传递的值相匹配的模式是不可反驳的。
    • 例如,语句 let x = 5; 中的 x因为 x 匹配任何内容,因此不可能匹配失败。
  • 无法匹配某些可能值的模式是可反驳的。
    • 例如,表达式 if let Some(x) = a_value 中的 Some(x),因为如果 a_value 变量中的值为 None 而不是 Some,则 Some(x) 模式将不匹配。

函数参数、let 语句和 for 循环只能接受不可反驳的模式,因为当值不匹配时程序无法执行任何有意义的操作。 if letwhile let 表达式接受可反驳和不可反驳的模式,但编译器会警告不可反驳的模式,因为根据定义,它们旨在处理可能的失败:条件的功能在于其能够根据成功或失败执行不同的操作。

让我们看一个例子,看看当我们尝试使用可反驳的模式时会发生什么,而 Rust 需要不可反驳的模式,反之亦然。清单 18-8 显示了一个 let 语句,但对于模式我们指定了 Some(x),这是一个可反驳的模式。正如您所料,这段代码将无法编译。

1
let Some(x) = some_option_value;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding
--> src/main.rs:3:9
|
3 | let Some(x) = some_option_value;
| ^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
= note: the matched value is of type `Option<i32>`
help: you might want to use `let else` to handle the variant that isn't matched
|
3 | let Some(x) = some_option_value else { todo!() };
| ++++++++++++++++

For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error

因为我们没有用 Some(x) 模式覆盖(也无法覆盖!)每个有效值,Rust 理所当然地会产生编译器错误。

如果我们有一个需要不可反驳模式的可反驳模式,我们可以通过更改使用该模式的代码来修复它:我们可以使用 if let,而不是使用 let。然后,如果模式不匹配,代码将跳过大括号中的代码,从而有效地继续。

1
2
3
if let Some(x) = some_option_value {
println!("{x}");
}

我们已经给出了代码!该代码现在完全有效。但是,如果我们给出 if let 一个不可反驳的模式(始终匹配的模式),例如 x,编译器将给出警告。

1
2
3
if let x = 5 {
println!("{x}");
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `if let` pattern
--> src/main.rs:2:8
|
2 | if let x = 5 {
| ^^^^^^^^^
|
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let`
= note: `#[warn(irrefutable_let_patterns)]` on by default

warning: `patterns` (bin "patterns") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/patterns`
5

因此,匹配臂必须使用可反驳的模式,最后一个臂除外,它应该将任何剩余的值与不可反驳的模式相匹配。 Rust 允许我们在只有一个分支的匹配中使用无可辩驳的模式,但这种语法并不是特别有用,可以用更简单的 let 语句替换。

0x08. 创建模式的所有语法

创建模式的所有语法