@@ -1,16 +1,10 @@
# 可使用模式的全部位置
# 可以 使用模式的所有地方
**All the Places Patterns Can Be Used **
模式出现在 Rust 中的很多地方,咱们可能在不知不觉中已经频繁使用了他们!这一小节讨论模式有效的所有地方。
## `match` 支臂
模式会出现在 Rust 中的数个地方,而咱们以及见到很多的使用他们而不自知!本小节会讨论模式有效的全部位置。
## `match` 的支臂
* * `match` Arms**
正如第 6 章中曾讨论过的,咱们是在 `match` 表达式的那些支臂中,使用模式的。形式上看,`match` 表达式是以关键字 `match` 、要匹配的某个值,以及由某个模式和在该值匹配此模式时,要运行的一个表达式组所成的一个或多个支臂,这种形式而被定义出的,就像下面这样:
正如第 6 章中所讨论的,我们在 `match` 表达式的支臂中使用模式。从形式上讲,`match` 表达式被定义为关键字 `match` 、要匹配的值,以及一个或多个匹配支臂,每个支臂由一个模式和一个在值与该支臂模式匹配时要运行的表达式构成,如下所示:
``` rust
match VALUE {
@@ -20,7 +14,7 @@ match VALUE {
}
```
比 如,下面就 是清单 6-5 中,在 变量 `x` 中一个 `Option<i32>` 值上匹配的那个 `match` 表达式 :
例 如,下面是 [ 清单 6-5 ](../enums_and_pattern_matching/match_control_flow.md#listing_6-5 ) 中的 `match` 表达式,匹配 变量 `x` 中的 `Option<i32>` 值:
``` rust
match x {
@@ -29,25 +23,80 @@ match x {
}
```
这个 `match` 表达式中的模式,便是各 个箭头左边 的 `None` 与 `Some(i)` 。
这个 `match` 表达式中的模式是每 个箭头左侧 的 `None` 和 `Some(i)` 。
`match` 表达式的一项要求,便是在表达式中那个值,所必须考虑到的全部可能性方面,需要 * 详尽无遗, exhaustive * 。而一种确保咱们已经覆盖每种可能性的方式,便是将一个捕获全部的 模式, a catchall pattern, 作为最后支臂:比如,一个 匹配任意 值的变量名称,就绝 不会失败,而因此会覆盖每种其 余的情形。
`match` 表达式的一项要求是,他们必须穷尽所有情况,即必须考虑 `match` 表达式中值的所有可能性。确保咱们覆盖所有可能性的一种方式是,对最后一个支臂使用全包 模式, a catchall pattern:例如 ,匹配任何 值的变量名字就永远 不会失败,而因此涵盖了所有剩 余的情形。
特定模式 `_` 将匹配任何内容,但他永远不会绑定到变量,因此常被用于最后一个支臂。例如,当咱们打算忽略任何未指定的值时,`_` 模式就非常有用。我们在本章后面的 [忽略模式中的值 ](./syntax.md#忽略模式中的值 ) 中更详细地介绍 `_` 模式。
特别的模式 `_` ,将匹配任何东西,但他绝不会绑定到某个变量,因此他通常被用在最后的匹配支臂中。在比如咱们打算忽略任何不予指定的值时,这种 `_` 模式就会是有用的。在本章稍后的 [“忽略模式中的值” ](#忽略模式中的某些值ignoring-values-in-a-pattern ) 小节中,咱们将更详细地讲到这种 `_` 模式。
## `let` 语句
在本章之前,我们只明确讨论过通过 `match` 和 `if let` 使用模式,但事实上,我们也在其他地方使用了模式,包括在 `let` 语句中。例如,请考虑下面这个通过 `let` 的简单变量赋值:
``` rust
let x = 5 ;
```
每当咱们使用这样的 `let` 语句时,咱们都在使用模式,尽管可能并没有意识到这一点!更正式地说,`let` 语句如下所示:
``` rust
let PATTERN = EXPRESSION ;
```
在像 `let x = 5;` 这样的语句中,当变量名处于 `PATTERN` 槽中时, 变量名只是模式的一种特别简单的形式。Rust 会将表达式与该模式比较,并指派他找到的任何名字。 因此,在 `let x = 5;` 这个示例中,`x` 是个模式,意思是 “绑定此处匹配的内容到变量 `x` ”。由于名字 `x` 就是整个模式,这个模式实际上意味着 “绑定所有内容到变量 `x` ,无论值是什么”。
为了更清楚地了解 `let` 的模式匹配方面,请考虑下面清单 19-1, 其中通过 `let` 使用模式来解构元组。
<a name="listing_19-1"></a>
``` rust
let ( x , y , z ) = ( 1 , 2 , 3 ) ;
```
**清单 19-1 ** :使用模式解构元组,并一次创建三个变量
在这里, 我们将元组与模式匹配。Rust 会将值 `(1, 2, 3)` 与模式 `(x, y, z)` 比较,并发现该值与模式匹配 —— 即二者中的元素的数量相同 —— 因此 Rust 会绑定 `1` 到 `x` , `2` 到 `y` , `3` 到 `z` 。咱们可以将这个元组模式,视为内部嵌套了三个单独变量的模式。
当模式中的元素数量与元组中的元素数量不匹配时,整体类型将不匹配,我们将得到编译器报错。例如,下清单 19-2 展示了一次将有着三个元素的元组解构为两个变量的尝试,但这是行不通的。
<a name="listing_19-2"></a>
``` rust
let ( x , y ) = ( 1 , 2 , 3 ) ;
```
**清单 19-2 ** :错误地构造了一个模式,其变量数量与元组的元素数量不匹配
尝试编译这段代码会导致以下类型报错:
``` console
$ cargo run
Compiling patterns v0.1.0 (/home/hector/rust-lang-zh_CN/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
```
要解决这一错误,我们可以使用 `_` 或 `..` 忽略元组中的一个或多个值,正如咱们将在 [忽略模式中的值 ](./syntax.md#忽略模式中的值 ) 小节中看到的那样。当问题在于模式中的变量过多时,解决方法是通过移除变量来使类型匹配,以便变量的数量等于元组中元素的数量。
## 条件 `if let` 表达式
**Conditional `if let` Expressions **
在 [第 6 章 ](../enums_and_pattern_matching/if-let_control_flow.md ) 中,我们讨论了怎样使用 `if let` 表达式,主要作为编写仅匹配一种情况的 `match` 表达式的等价的简短方式。此外,`if let` 还可以有一个相应的 `else` ,在 `if let` 中的模式不匹配时包含代码。
下面清单 19-3 展示了还可以混合使用 `if let` 、`else if` 和 `else if let` 表达式。这样做给予我们相比 `match` 表达式更大的灵活性,在 `match` 表达式中我们只能表达一个值与模式比较。此外, Rust 不要求一系列 `if let` 、`else if` 、`else if let` 支臂中的条件相互关联。
在第 6 章中,咱们曾讨论过怎样将 `if let` 表达式主要用于编写,只与一种情形匹配的 `match` 表达式等价的简便方式。`if let` 可选地能有包含了在 `if let` 中模式不匹配时,要运行代码的一个相应 `else` 。
下面清单 18-1 显示,混用及匹配 `if let` 、`else if` 及 `else if let` 这些表达式是可行的。相比于其中咱们只能表达出,与一些模式匹配的唯一一个值的 `match` 表达式, 这样做就会给到更多灵活性。并且, Rust 不要求一系列 `if let` 、`else if` 、`else if let` 支臂中的那些条件相互有关联。
清单 18-1 中的代码,根据数个条件的一系列检查,而判断出构造绑架的何种颜色。对于这个示例,咱们已创建了有着真实程序中,本应从用户输入接收到,但这里是一些硬编码值的数个变量。
清单 19-3 中的代码会根据多个条件的一系列检查,类确定构成咱们背景的颜色。对于这个示例,我们以硬编码的值创建了变量,这些硬编码值在真实程序中可能来自用户输入 。
<a name="listing_19-3"></a>
文件名:`src/main.rs`
``` rust
@@ -72,45 +121,45 @@ fn main() {
}
```
* 清单 18-1: 混 用 `if let`、 `else if`、 `else if let` 及 `else`*
**清单 19-3 ** :混合使 用 `if let` 、 `else if` 、 `else if let` 及 `else`
在 用户指定了喜好的颜色时,那种 颜色就 会被用作背景。若 没有指定喜好颜色而 今天是周二,背景颜色就是 绿色。否则,在 用户以字符串指定了他们的年龄,而咱们可以将其成功 解析为数字时,根据该数字的值,颜色就会要么时紫色,抑或是 橙色。而在 这些条件都不适用时,背景颜色就会是 蓝色。
当 用户指定了喜好的颜色时,该 颜色会被用作背景。当 没有指定喜好颜色并且 今天是周二时 ,背景颜色为 绿色。否则,当 用户以字符串形式 指定了他们的年龄,并且我们可以成功将其 解析为数字时,则 根据该数字的值,颜色为紫色或 橙色。当 这些条件都不适用时,背景颜色为 蓝色。
这种条件结构,实现了对复杂要求的支持。在这里咱们所拥有的那些 硬编码值下,这个示例将打印出 `使用紫色作为背景色` 。
这种条件结构使我们可以支持复杂的需求。在这里的 硬编码值下,这个示例将打印 `使用紫色作为背景色` 。
咱们可以看到,`if let` 也能以 `match` 支臂所能够 的同样方式,引入一些遮蔽变量, shadowed variables :其中行 `if let Ok(age) = age` 就 引入了包含着在那个 `Ok` 变种里值的一个新遮蔽 `age` 变量 。这意味着咱 们需要把 `if age > 30` 情形,放在该代码块里:咱们不能将这两种情形,结合进到 `if let Ok(age) = age && age > 30` 。咱 们打算将其 与 `30` 比较的那个遮蔽 `age` ,直到那个新代码块以花括号开头之前,都还 是无效的。
咱们可以看到,`if let` 还可以与 `match` 支臂的同样方式,引入遮蔽现有变量的新变量 :其中 `if let Ok(age) = age` 行 引入了一个新的 `age` 变量,包含 `Ok` 变种内的值 。这意味着我 们需要放置 `if age > 30` 的条件于该代码块中:我们无法合并这两个条件为 `if let Ok(age) = age && age > 30` 。在新的作用域以花括号开始前,我 们打算与 `30` 比较的新 `age` 是无效的。
使用 `if let` 表达式的缺点,便 是编译器不会就穷尽加以 检查,而在 `match` 表达式下则会。若咱 们省略了其中 最后的 `else` 代码块,而因此遗漏了处理某些情形 ,编译器也 不会就 可能的逻辑错误向我们发出告警 。
使用 `if let` 表达式的缺点是, 编译器不会检查是否详尽 ,而对 `match` 表达式他会检查。当我 们省略了最后的 `else` 代码块,而因此遗漏了处理某些情况时 ,编译器将 不会提醒我们 可能的逻辑 bug 。
## `while let` 条件循环
与 `if let` 的结构类似,`while let` 条件循环允许 `while` 循环在模式持续匹配的情况下一直运行。在下面清单 19-4 中,我们展示了一个 `while let` 循环,等待线程之间发送的消息,但在这一情形下检查 `Result` 而非 `Option` 。
与 `if let` 的构造类似,`while let` 条件循环允许某个 `while` 循环,在某个模式持续匹配期间运行。下面清单 18-2 中,咱们编写了将一个矢量用作栈,并以该矢量中那些值被压入的相反顺序,将这些值打印处理的一个 `while let` 循环。
<a name="listing_19-4"></a>
``` rust
let mut stack = Vec :: new ( ) ;
stack . push ( 1 ) ;
stack . push ( 2 ) ;
stack . push ( 3 ) ;
while let Some ( top ) = stack . pop ( ) {
println! ( " {} " , top ) ;
let ( tx , rx ) = std ::sync ::mpsc ::chan nel ( ) ;
std ::thread ::spawn ( move | | {
for val in [ 1 , 2 , 3 ] {
tx . send ( val ) . unwrap ( ) ;
}
} ) ;
while let Ok ( value ) = rx . recv ( ) {
println! ( " {value} " ) ;
}
```
* 清单 18-2 :使用 `while let` 循环,来于 `stack.pop ()` 返回 `Some` 期间打印出一些值 *
**清单 19-4 ** :使用 `while let` 循环,来在 `rx.recv ()` 返回 `Ok` 期间打印值
此 示例打印出 `3 ` 、`2` 及随后的 `1 ` 。`pop ` 方法会取出矢量值的最后一个元素 ,并返回 `Some (value)` 。在该矢量值为空时,`pop` 就会返回 `None` ,这个循环便停止。咱们可以使用 `while let` 循环,弹出栈中的每个元素 。
这个 示例会 打印 `1 ` 、`2` ,然后是 `3 ` 。`recv ` 方法从信道的接收端取出第一条消息 ,并返回一个 `Ok (value)` 。当我们在第 16 章中第一次看到 `recv` 时,我们曾直接解包错误,或通过使用 `for` 循环将其作为迭代器与其交互。但如清单 19-4 所示,我们也可以使用 `while let` ,因为只要发送方存在,`recv` 方法就会在每次消息到达时都返回 `Ok` ,而一旦发送方断开连接就会返回 `Err` 。
## `for` 循环
在 `for` 循环中,紧跟关键字 `for` 之后的值即为模式。例如,在 `for x in y` 中,`x` 就是模式。下面清单 19-5 演示了如何在 `for` 循环使用模式解构,或者拆解元组,作为 `for` 循环的一部分。
在 `for` 循环中,直接跟在关键字 `for` 之后的那个值,就是个模式。比如,在 `for x in y` 中,`x` 就是其中的模式。下面清单 18-3 演示了如何使用 `for` 循环中的模式,来结构,或者说拆分作为这个 `for` 循环一部分的某个元组。
<a name="listing_19-5"></a>
``` rust
fn main ( ) {
let v = vec! [ 'a' , 'b' , 'c' ] ;
@@ -121,104 +170,40 @@ fn main() {
}
```
* 清单 18-3: 使用一个 `for` 循环中的模式,来结构某个元组 *
**清单 19-5 ** :在 `for` 循环使用模式解构元组
清单 18-3 中的代码, 将打印出如 下内容:
清单 19-5 中的代码将打印出以 下内容:
``` console
$ cargo run lennyp@vm-manjaro
Compiling for_demo v0.1.0 (/home/lennyp /rust-lang/for_demo )
Finished dev [unoptimized + debuginfo] target(s) in 0.25 s
Running `target/debug/for_demo `
$ cargo run
Compiling patterns v0.1.0 (/home/hector /rust-lang-zh_CN/projects/patterns )
Finished ` dev` profile [unoptimized + debuginfo] target(s) in 0.07 s
Running `target/debug/patterns `
a 处于索引 0 处
b 处于索引 1 处
c 处于索引 2 处
```
咱 们使用 `enumerate` 方法适配了一个 迭代器,如此他便会产生放入到元组中的一个值,以及那个值的索引。首个产生出的值,为 元组 `(0, 'a')` 。当这个值与模式 `(index, value)` 匹配时,`index` 将为 `0` , 同时 `value` 将为 `'a'` ,从而打印出 输出的首 行。
## `let` 语句
本章之前,咱们只明确讨论过 `match` 与 `if let` 下对模式的运用,但事实上,咱们也曾在别的地方用到过模式,包括在 `let` 语句中。比如,请考虑下面这个使用 `let` 的简单变量赋值:
``` rust
let x = 5 ;
```
在咱们每次像这样使用 `let` 语句,就都用到了模式,尽管咱们可能并未意识到他!更正式地说,`let` 语句看起来是这样的:
``` rust
let PATTERN = EXPRESSION ;
```
在像是 `let x = 5;` 这样,于 `PATTERN` 槽处有着一个变量名的语句中, 那个变量名正是模式的一种特别简单形式。Rust 会将表达式与该模式比较, 而赋予其找到的任何名字, Rust compares the expression against the pattern and assigns any names it finds。因此比如在 `let x = 5;` 中,`x` 就是一个表示 “将这里所匹配的东西,绑定到变量 `x` , bind what matches here to the variable `x` 。” 由于名字 `x` 为整个的模式,那么此模式便意味着 “将所有东西都绑定到变量 `x` , 不管值为何, bind everything to the variable `x` , whatever the value is。”
要更清楚地看到 `let` 的模式匹配方面,请考虑下面清单 18-4, 他在 `let` 下使用了一个模式,来结构某个元组。
``` rust
let ( x , y , z ) = ( 1 , 2 , 3 ) ;
```
* 清单 18-4: 使用模式来结构元组, 并一次性创建处三个变量 *
在这里, 咱们将一个元组与某个模式匹配。Rust 会比较把值 `(1, 2, 3)` ,与模式 `(x, y, z)` 相比较,并发现该值与这种模式匹配,因此 Rust 就把 `1` 绑定到 `x` , `2` 绑定到 `y` ,而把 `3` 绑定到 `z` 。咱们可把这种元组模式,设想为在其中嵌套了三个单独的变量。
而当模式中元素个数,不与元组中元素个数匹配时,整体的类型就不会匹配,同时咱们将得到一个编译器报错。比如,下面清单 18-5 给出了三个元素解构到两个变量的尝试,这将不会工作。
``` rust
let ( x , y ) = ( 1 , 2 , 3 ) ;
```
* 清单 18-5: 不正确的构造模式, 其变量与元组中元素个数不匹配 *
尝试编译此代码会导致如下这种类型报错:
``` console
$ cargo run lennyp@vm-manjaro
Compiling while_let_demo v0.1.0 (/home/lennyp/rust-lang/while_let_demo)
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 `while_let_demo` due to previous error
```
要修复该错误,咱们可以如同即将在 [于模式中忽略一些值 ](#忽略模式中的某些值ignoring-values-in-a-pattern ) 小节中所看到的那样,使用 `_` 或 `..` ,忽略那个元组中的一个或更多的值。当问题是咱们在模式中有太多变量时,那么办法就是通过移除一些变量构造出类型,从而变量数目便等于元组中元素的数目了。
我 们使用 `enumerate` 方法适配迭代器,以便他生成值及该值的索引,便放入元组中。生成的第一个值是 元组 `(0, 'a')` 。当这个值与模式 `(index, value)` 匹配时,`index` 将为 `0` , `value` 将为 `'a'` ,从而打印输出的第一 行。
## 函数参数
**Function Parameters **
函数参数也可以是些模式。下面清单 18-6 中的代码,声明了取名为 `x` 类型 `i32` 的一个叫做 `foo` 的函数,现在看起来应不陌生。
函数参数也可以是模式。下面清单 19-6 中的代码,声明了一个名为 `foo` 的函数,取一个名为 `x` 类型 `i32` 的参数,现在看起来应该很很熟悉。
<a name="listing_19-6"></a>
``` rust
fn foo ( x : i32 ) {
// 代码出现 在这里
// 代码放 在这里
}
```
* 清单 18 -6: 在参数中用到 模式的一个 函数签名*
** 清单 19 -6** :在参数中使 用模式的函数签名
其中那个 `x` 部分, 便是个模式!与咱们曾在 `let` 下 所做的那样,咱 们可以在 函数参数中,将某个 元组和 模式匹配。下面清单 18 -7 就在咱们把某个元组传递给一个 函数时,拆分了其中的那些值:
文件名:`src/main.rs`
``` rust
其 中 那 个 ` x ` 部 分 , 便 是 个 模 式 ! 正 如 咱 们 在 ` let ` 下 曾 做 的 那 样 , 咱 们 可 将 函 数 参 数 中 的 某 个 元 组 , 与 模 式 匹 配 。 下 面 清 单 18 - 7 , 就 在 咱 们 将 某 个 元 组 传 递 给 一 个 函 数 时 , 拆 分 了 其 中 的 那 些 值 。
`x` 部分便是个模式!就像我们对 `let` 所做的那样,我 们可以将 函数参数中的 元组与 模式匹配。下面清单 19 -7 在我们传递元组给 函数时,拆分了其中的值。
<a name="listing_19-7"></a>
文件名:`src/main.rs`
``` rust
@@ -232,14 +217,13 @@ fn main() {
}
```
* 清单 18 -7:有着一些解构某个 元组参数的一个函数 *
** 清单 19 -7** :带有解构 元组的 参数的函数
此 代码会打印出 `当前坐标:(3, -5)` 。值 `&(3, -5)` 匹配了 模式 `&(x, y)` ,因此 `x` 为值 `3` 而 `y` 就是 值 `-5` 。
这段 代码会打印 `当前坐标:(3, -5)` 。值 `&(3, -5)` 匹配模式 `&(x, y)` ,因此 `x` 为值 `3` , `y` 为 值 `-5` 。
由于如在第 13 章中所讨论的那样,闭包与函数类似,因此我们也可以在闭包的参数列表中,以与函数的参数列表相同的方式使用模式。
由于如咱们在第 13 章中曾讨论过的,闭包与函数类似,咱们也可以函数参数清单中的这同样方式,在闭包参数清单中使用模式 。
到这里, 咱们就已经看到了运用模式的数种方式, 但在咱们可使用他们的每种地方, 模式并非以同样方式运作。在一些地方, 模式必须是确凿的, must be irrefutable; 而在别的情况下, 他们则可以是可证伪的, can be refutable。接下来咱们就将讨论这两个概念。
至此,咱们就已经了解了数种使用模式的方式,但模式在我们可以使用他们的每个地方,都以相同的方式工作。在一些地方,模式必须是不可证伪的;而在其他情况下,他们可以是可证伪的。接下来我们将讨论这两个概念 。
( End)