所有模式可以应用的地方
模式在 Rust 中很多地方都会出现,而你一直在使用它们,只是没有意识到!本节讨论模式有效的所有地方。
match
分支
正如第 6 章所讨论的,我们在 match
表达式的分支中使用模式。形式上,match
表达式被定义为关键字 match
,一个要匹配的值,以及一个或多个匹配分支,这些分支由一个模式和一个表达式组成,如果值与该分支的模式匹配,则运行该表达式,像这样
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
例如,这是来自列表 6-5 的 match
表达式,它匹配变量 x
中的 Option<i32>
值
match x {
None => None,
Some(i) => Some(i + 1),
}
这个 match
表达式中的模式是每个箭头左侧的 None
和 Some(i)
。
match
表达式的一个要求是它们需要是穷尽的,即 match
表达式中值的所有可能性都必须被考虑到。确保你涵盖了每种可能性的一种方法是为最后一个分支设置一个捕获所有模式:例如,匹配任何值的变量名永远不会失败,因此涵盖了所有剩余情况。
特定的模式 _
将匹配任何内容,但它永远不会绑定到变量,因此它经常在最后一个匹配分支中使用。例如,当你想忽略任何未指定的值时,_
模式会很有用。我们将在本章后面的 “在模式中忽略值”节中更详细地介绍 _
模式。
条件 if let
表达式
在第 6 章中,我们讨论了如何使用 if let
表达式,主要是作为编写等效于只匹配一种情况的 match
的更短方法。可选地,if let
可以有一个对应的 else
,其中包含如果 if let
中的模式不匹配时要运行的代码。
列表 19-1 表明,也可以混合和匹配 if let
、else if
和 else if let
表达式。这样做比 match
表达式给我们更多的灵活性,在 match
表达式中,我们只能表达一个要与模式比较的值。此外,Rust 不要求一系列 if let
、else if
、else if let
分支中的条件相互关联。
列表 19-1 中的代码根据对几个条件的系列检查来确定将背景设置为哪种颜色。对于此示例,我们创建了具有硬编码值的变量,真实程序可能会从用户输入中接收这些值。
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");
}
}
if let
、else if
、else if let
和 else
如果用户指定了喜欢的颜色,则该颜色将用作背景。如果未指定喜欢的颜色且今天是星期二,则背景颜色为绿色。否则,如果用户将其年龄指定为字符串,并且我们可以成功将其解析为数字,则颜色为紫色或橙色,具体取决于数字的值。如果这些条件都不适用,则背景颜色为蓝色。
这种条件结构使我们能够支持复杂的需求。使用我们在这里的硬编码值,此示例将打印 Using purple as the background color
。
你可以看到 if let
也可以引入新的变量,这些变量以与 match
分支相同的方式遮蔽现有变量:行 if let Ok(age) = age
引入了一个新的 age
变量,其中包含 Ok
变体内的值,从而遮蔽了现有的 age
变量。这意味着我们需要将 if age > 30
条件放在该块内:我们不能将这两个条件组合成 if let Ok(age) = age && age > 30
。我们想要与 30 进行比较的新 age
在新作用域以花括号开始之前无效。
使用 if let
表达式的缺点是编译器不检查穷尽性,而 match
表达式会检查。如果我们省略了最后一个 else
块,因此错过了处理某些情况,则编译器不会提醒我们可能的逻辑错误。
while let
条件循环
与 if let
结构类似,while let
条件循环允许 while
循环只要模式继续匹配就一直运行。我们在第 17 章中首次看到了 while let
循环,我们在那里使用它来保持循环,只要流产生新值。类似地,在列表 19-2 中,我们展示了一个 while let
循环,它等待线程之间发送的消息,但在这种情况下检查的是 Result
而不是 Option
。
while let
循环打印值,只要 rx.recv()
返回 Ok
此示例打印 1、2 和 3。当我们在第 16 章中看到 recv
时,我们直接解包了错误,或者使用 for
循环将其作为迭代器进行交互。但是,正如列表 19-2 所示,我们也可以使用 while let
,因为 recv
方法只要发送者正在生成消息就返回 Ok
,然后在发送者端断开连接后产生 Err
。
for
循环
在 for
循环中,紧跟关键字 for
的值是一个模式。例如,在 for x in y
中,x
是模式。列表 19-3 演示了如何在 for
循环中使用模式来解构或分解元组作为 for
循环的一部分。
for
循环中使用模式来解构元组列表 19-3 中的代码将打印以下内容
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
我们使用 enumerate
方法调整迭代器,使其生成一个值和该值的索引,并将它们放入一个元组中。生成的第一个值是元组 (0, 'a')
。当此值与模式 (index, value)
匹配时,index
将为 0
,value
将为 'a'
,从而打印输出的第一行。
let
语句
在本章之前,我们只明确讨论过将模式与 match
和 if let
一起使用,但实际上,我们也在其他地方使用了模式,包括在 let
语句中。例如,考虑使用 let
的这种直接变量赋值
每次你像这样使用 let
语句时,你都在使用模式,尽管你可能没有意识到!更正式地说,let
语句看起来像这样
let PATTERN = EXPRESSION;
在像 let x = 5;
这样的语句中,PATTERN
位置中有一个变量名,变量名只是模式的一种特别简单的形式。Rust 将表达式与模式进行比较,并分配它找到的任何名称。因此,在 let x = 5;
示例中,x
是一种模式,意思是“将此处匹配的内容绑定到变量 x
”。因为名称 x
是整个模式,所以此模式实际上意味着“将所有内容绑定到变量 x
,无论值是什么。”
为了更清楚地看到 let
的模式匹配方面,请考虑列表 19-4,该列表使用带有 let
的模式来解构元组。
在这里,我们将元组与模式匹配。Rust 将值 (1, 2, 3)
与模式 (x, y, z)
进行比较,并看到该值与模式匹配,因此 Rust 将 1
绑定到 x
,2
绑定到 y
,3
绑定到 z
。你可以将此元组模式视为在其内部嵌套了三个单独的变量模式。
如果模式中元素的数量与元组中元素的数量不匹配,则整体类型将不匹配,我们将收到编译器错误。例如,列表 19-5 显示了尝试将具有三个元素的元组解构为两个变量,这将不起作用。
尝试编译此代码会导致此类型错误
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
要修复此错误,我们可以使用 _
或 ..
忽略元组中的一个或多个值,正如你将在 “在模式中忽略值”节中看到的那样。如果问题是我们在模式中有太多变量,则解决方案是通过删除变量来使类型匹配,以便变量的数量等于元组中元素的数量。
函数参数
函数参数也可以是模式。列表 19-6 中的代码声明了一个名为 foo
的函数,该函数接受一个名为 x
的 i32
类型的参数,现在应该看起来很熟悉。
x
部分是一个模式!正如我们在 let
中所做的那样,我们可以在函数的参数中将元组与模式匹配。列表 19-7 将元组中的值拆分,当我们将其传递给函数时。
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
此代码打印 Current location: (3, 5)
。值 &(3, 5)
与模式 &(x, y)
匹配,因此 x
是值 3
,y
是值 5
。
我们也可以在闭包参数列表中使用模式,方式与函数参数列表中相同,因为闭包与函数类似,如第 13 章中所讨论的。
至此,你已经看到了几种使用模式的方法,但模式在我们可以使用它们的每个地方的工作方式都不同。在某些地方,模式必须是不可反驳的;在其他情况下,它们可以是可反驳的。我们将在接下来讨论这两个概念。