Rust: 模式与匹配
0x01. 模式
模式是 Rust 中用于匹配复杂和简单类型的结构的特殊语法。将模式与匹配表达式和其他结构结合使用,可以更好地控制程序的控制流。模式由以下内容的组合组成:
- Literals
- Destructured arrays, enums, structs, or tuples
- Variables
- Wildcards
- Placeholders
0x02. match
控制流
Rust 有一个非常强大的控制流构造,称为 match
,它允许您将一个值与一系列模式进行比较,然后根据模式匹配执行代码。模式可以由文字值、变量名、通配符和许多其他内容组成;
1 | match VALUE { |
1 | enum Coin { |
0x03. if let
简洁控制流
if let
语法让你可以将 if
和 let
组合成一种更简洁的方式,以处理匹配一个模式的值而忽略其余的值。
使用 match
编写的代码
1 | let config_max = Some(3u8); |
使用 if let
以更短的方式编写代码
1 | let config_max = Some(3u8); |
使用 else
匹配其余情况
与 else
搭配的代码块与 match
表达式中与 _
情况搭配的代码块相同,这相当于 if let
和 else
。
1 | let mut count = 0; |
综合例子
1 | fn main() { |
0x04. while let
条件循环
与 if let 结构类似,while let 条件循环允许 while 循环在模式持续匹配时一直运行。
1 | let mut stack = Vec::new(); |
0x05. for
循环
在 for 循环中,关键字 for 后面的值是模式。例如,在 for x in y 中,x 就是模式。
1 | let v = vec!['a', 'b', 'c']; |
我们使用枚举方法调整迭代器,以便它生成一个值和该值的索引,并将其放入元组中。生成的第一个值是元组 (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 | fn print_coordinates(&(x, y): &(i32, i32)) { |
0x07. 可反驳性:模式是否可能无法匹配
模式有两种形式:可反驳的和不可反驳的。
- 与任何可能传递的值相匹配的模式是不可反驳的。
- 例如,语句 let x = 5; 中的 x因为 x 匹配任何内容,因此不可能匹配失败。
- 无法匹配某些可能值的模式是可反驳的。
- 例如,表达式 if let Some(x) = a_value 中的 Some(x),因为如果 a_value 变量中的值为 None 而不是 Some,则 Some(x) 模式将不匹配。
函数参数、let
语句和 for
循环只能接受不可反驳的模式,因为当值不匹配时程序无法执行任何有意义的操作。 if let
和 while let
表达式接受可反驳和不可反驳的模式,但编译器会警告不可反驳的模式,因为根据定义,它们旨在处理可能的失败:条件的功能在于其能够根据成功或失败执行不同的操作。
让我们看一个例子,看看当我们尝试使用可反驳的模式时会发生什么,而 Rust 需要不可反驳的模式,反之亦然。清单 18-8 显示了一个 let 语句,但对于模式我们指定了 Some(x),这是一个可反驳的模式。正如您所料,这段代码将无法编译。
1 | let Some(x) = some_option_value; |
1 | $ cargo run |
因为我们没有用 Some(x) 模式覆盖(也无法覆盖!)每个有效值,Rust 理所当然地会产生编译器错误。
如果我们有一个需要不可反驳模式的可反驳模式,我们可以通过更改使用该模式的代码来修复它:我们可以使用 if let
,而不是使用 let
。然后,如果模式不匹配,代码将跳过大括号中的代码,从而有效地继续。
1 | if let Some(x) = some_option_value { |
我们已经给出了代码!该代码现在完全有效。但是,如果我们给出 if let
一个不可反驳的模式(始终匹配的模式),例如 x,编译器将给出警告。
1 | if let x = 5 { |
1 | $ cargo run |
因此,匹配臂必须使用可反驳的模式,最后一个臂除外,它应该将任何剩余的值与不可反驳的模式相匹配。 Rust 允许我们在只有一个分支的匹配中使用无可辩驳的模式,但这种语法并不是特别有用,可以用更简单的 let
语句替换。