wip: 2024 edition

pull/875/head
KaiserY 2 weeks ago
parent 28ff0a198d
commit 012d7b71aa

@ -1,13 +1,12 @@
# 模式与模式匹配
> [ch19-00-patterns.md](https://github.com/rust-lang/book/blob/main/src/ch19-00-patterns.md)
> <br>
> commit 6fce661a0938aa0da06526e7b8f98fd7e67a222f
<!-- https://github.com/rust-lang/book/blob/main/src/ch19-00-patterns.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
**模式***Patterns*)是 Rust 中特殊的语法,它用来匹配类型的结构,无论类型是简单还是复杂。结合使用模式和 `match` 表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成:
**模式***Patterns*)是 Rust 中一种特殊的语法,它用来匹配类型的结构,无论类型是简单还是复杂。结合使用模式和 `match` 表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成:
* 字面值
* 解构的数组、枚举、结构体或者元组
* 解构的数组、枚举、结构体或者元组
* 变量
* 通配符
* 占位符
@ -16,4 +15,4 @@
我们通过将一些值与模式相比较来使用它。如果模式匹配这些值,我们对值部分进行相应处理。回忆一下第六章讨论 `match` 表达式时像硬币分类器那样使用模式。如果数据符合这个形状,就可以使用这些命名的片段。如果不符合,与该模式相关的代码则不会运行。
本章是所有模式相关内容的参考。我们将涉及到使用模式的有效位置,*refutable* 与 *irrefutable* 模式的区别,和你可能会见到的不同类型的模式语法。在最后,你将会看到如何使用模式创建强大而简洁的代码
本章是所有模式相关内容的参考。我们将涉及到使用模式的有效位置,*refutable* 与 *irrefutable* 模式的区别,和你可能会见到的不同类型的模式语法。到本章末尾,你就能掌握如何利用模式以清晰的方式表达多种概念

@ -1,14 +1,13 @@
## 所有可能会用到模式的位置
> [ch19-01-all-the-places-for-patterns.md](https://github.com/rust-lang/book/blob/main/src/ch19-01-all-the-places-for-patterns.md)
> <br>
> commit 0c2d41e2afce734825c3a12087d423e8c2f0ae53
<!-- https://github.com/rust-lang/book/blob/main/src/ch19-01-all-the-places-for-patterns.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
模式出现在 Rust 的很多地方。你已经在不经意间使用了很多模式!本部分是一个所有有效模式位置的参考
模式出现在 Rust 的很多地方。你已经在不经意间使用了很多模式!本节将介绍所有模式有效的位置
### `match` 分支
如第六章所讨论的,一个模式常用的位置是 `match` 表达式的分支。在形式上 `match` 表达式由 `match` 关键字、用于匹配的值和一个或多个分支构成,这些分支包含一个模式和在值匹配分支的模式时运行的表达式:
如第六章所讨论的,一个模式常用的位置是 `match` 表达式的分支。在形式上 `match` 表达式由 `match` 关键字、用于匹配的值和一个或多个分支构成,这些分支包含一个模式和在值匹配分支的模式时运行的表达式,如下所示
```text
match VALUE {
@ -29,7 +28,7 @@ match x {
这个 `match` 表达式中的模式为每个箭头左边的 `None``Some(i)`
`match` 表达式必须**穷尽***exhaustive*)的,意为 `match` 表达式所有可能的值都必须被考虑到。一个确保覆盖每个可能值的方法是在最后一个分支使用捕获所有的模式:比如,一个匹配任何值的名称永远也不会失败,因此可以覆盖所有匹配剩下的情况。
`match` 表达式的一个要求是它们必须**穷尽***exhaustive*)的,意为 `match` 表达式所有可能的值都必须被考虑到。一个确保覆盖每个可能值的方法是在最后一个分支使用捕获所有的模式:比如,一个匹配任何值的名称永远也不会失败,因此可以覆盖所有匹配剩下的情况。
有一个特定的模式 `_` 可以匹配所有情况,不过它从不绑定任何变量。这在例如希望忽略任何未指定值的情况很有用。本章之后的 [“忽略模式中的值”][ignoring-values-in-a-pattern] 部分会详细介绍 `_` 模式的更多细节。
@ -39,7 +38,7 @@ match x {
示例 19-1 展示了也可以组合并匹配 `if let`、`else if` 和 `else if let` 表达式。这相比 `match` 表达式一次只能将一个值与模式比较提供了更多灵活性。并且 Rust 并不要求一系列 `if let`、`else if`、`else if let` 分支的条件相互关联。
示例 19-1 中的代码展示了一系列针对不同条件的检查来决定背景颜色应该是什么。为了达到这个例子的目的,我们创建了硬编码值的变量,在真实程序中则可能由询问用户获得
示例 19-1 中的代码展示了一系列针对不同条件的检查来决定背景颜色应该是什么。为了达到这个例子的目的,我们创建了硬编码值的变量,真实程序中这些值可能来源于用户输入
<span class="filename">文件名src/main.rs</span>
@ -53,13 +52,13 @@ match x {
这个条件结构允许我们支持复杂的需求。使用这里硬编码的值,例子会打印出 `Using purple as the background color`
注意 `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` 分支那样引入并遮蔽现有变量:`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` 循环。我们在第十七章第一次见到 `while let` 循环,当时我们使用它在流还在产生新值的时候保持循环。同理在示例 19-2 展示了一个 `while let` 循环等待跨线程发送的消息,不过在这个示例中它检查一个 `Result`不是 `Option`
一个与 `if let` 结构类似的是 `while let` 条件循环,它允许只要模式匹配就一直进行 `while` 循环。在示例 19-2 展示了一个 `while let` 循环等待跨线程发送的消息,不过在这个示例中它检查一个 `Result` `Option`
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-02/src/main.rs:here}}
@ -67,11 +66,11 @@ match x {
<span class="caption">示例 19-2: 使用 `while let` 循环只要 `rx.recv()` 返回 `Ok` 就打印出其值</span>
这个例子会打印出 1、2 和 3。当在第十六章遇到 `recv` 时,我们直接 unwrap 了错误,或者使用 `for` 循环将其视为迭代器处理。不过如示例 19-2 所示,我们也可以使用 `while let`,因为 `recv` 方法只要发送端持续产生消息它就一直返回 `Ok`,并在发送端断开连接后产生一个 `Err`
这个例子会打印出 `1``2``3`。`recv` 方法从信道的接收端取出第一条消息并返回一个 `Ok(value)`。当在第十六章遇到 `recv` 时,我们直接 unwrap 了错误,或者使用 `for` 循环将其视为迭代器处理。不过如示例 19-2 所示,我们也可以使用 `while let`,因为 `recv` 方法只要发送端持续产生消息它就一直返回 `Ok`,并在发送端断开连接后产生一个 `Err`
### `for` 循环
`for` 循环中,模式是 `for` 关键字直接跟随的值,正如 `for x in y` 中的 `x`。示例 19-3 中展示了如何使用 `for` 循环来解构,或拆开一个元组作为 `for` 循环的一部分:
`for` 循环中,模式是 `for` 关键字直接跟随的值。例如,在 `for x in y` 中,`x` 就是这个模式。示例 19-3 中展示了如何使用 `for` 循环来解构,或拆开一个元组作为 `for` 循环的一部分:
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-03/src/main.rs:here}}
@ -85,11 +84,11 @@ match x {
{{#include ../listings/ch19-patterns-and-matching/listing-19-03/output.txt}}
```
这里使用 `enumerate` 方法适配一个迭代器来产生一个值和其在迭代器中的索引,它们位于一个元组中。第一个产生的值是元组 `(0, 'a')`。当这个值匹配模式 `(index, value)``index` 将会是 0 而 `value` 将会是 `'a'`,并打印出第一行输出。
这里使用 `enumerate` 方法适配一个迭代器来产生一个值和其在迭代器中的索引,它们位于一个元组中。第一个产生的值是元组 `(0, 'a')`。当这个值匹配模式 `(index, value)``index` 将会是 `0``value` 将会是 `'a'`,并打印出第一行输出。
### `let` 语句
在本章之前,我们只明确讨论过通过 `match``if let` 使用模式,不过事实上也在别的地方使用过模式,包括 `let` 语句。例如,考虑一下这个直白的 `let` 变量赋值:
在本章之前,我们只明确讨论过通过 `match``if let` 使用模式,不过事实上也在别的地方使用过模式,包括 `let` 语句。例如,考虑一下这个直白的 `let` 变量赋值:
```rust
let x = 5;
@ -111,7 +110,7 @@ let PATTERN = EXPRESSION;
<span class="caption">示例 19-4: 使用模式解构元组并一次创建三个变量</span>
这里将一个元组与模式匹配。Rust 会比较值 `(1, 2, 3)` 与模式 `(x, y, z)` 并发现此值匹配这个模式。在这个例子中,将会把 `1` 绑定到 `x``2` 绑定到 `y``3` 绑定到 `z`。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。
这里将一个元组与模式匹配。Rust 会比较值 `(1, 2, 3)` 与模式 `(x, y, z)`,并发现二者具有相同的元素数量,因此匹配成功,于是将 `1` 绑定到 `x`,将 `2` 绑定到 `y``3` 绑定到 `z`。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。
如果模式中元素的数量不匹配元组中元素的数量,则整个类型不匹配,并会得到一个编译时错误。例如,示例 19-5 展示了尝试用两个变量解构三个元素的元组,这是不行的:
@ -139,7 +138,7 @@ let PATTERN = EXPRESSION;
<span class="caption">列表 19-6: 在参数中使用模式的函数签名</span>
`x` 部分就是一个模式!类似于之前对 `let` 所做的,可以在函数参数中匹配元组。列表 19-7 将传递给函数的元组拆分为值:
`x` 部分就是一个模式!类似于之前对 `let` 所做的,可以在函数参数中匹配元组。列表 19-7 将传递给函数的元组拆分为各个值:
<span class="filename">文件名src/main.rs</span>
@ -149,7 +148,7 @@ let PATTERN = EXPRESSION;
<span class="caption">列表 19-7: 一个在参数中解构元组的函数</span>
这会打印出 `Current location: (3, 5)`。值 `&(3, 5)` 会匹配模式 `&(x, y)`,如此 `x` 得到了值 `3`,而 `y`得到了值 `5`
这会打印出 `Current location: (3, 5)`。值 `&(3, 5)` 会匹配模式 `&(x, y)`,如此 `x` 得到了值 `3`,而 `y` 得到了值 `5`
因为如第十三章所讲闭包类似于函数,也可以在闭包参数列表中使用模式。

@ -1,22 +1,21 @@
## Refutability可反驳性: 模式是否会匹配失效
> [ch19-02-refutability.md](https://github.com/rust-lang/book/blob/main/src/ch19-02-refutability.md)
> <br>
> commit 0c2d41e2afce734825c3a12087d423e8c2f0ae53
<!-- https://github.com/rust-lang/book/blob/main/src/ch19-02-refutability.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
模式有两种形式refutable可反驳的和 irrefutable不可反驳的。能匹配任何传递的可能值的模式被称为是 **不可反驳的***irrefutable*)。一个例子就是 `let x = 5;` 语句中的 `x`,因为 `x` 可以匹配任何值所以不可能会失败。对某些可能的值进行匹配会失败的模式被称为是 **可反驳的***refutable*)。一个这样的例子便是 `if let Some(x) = a_value` 表达式中的 `Some(x)`;如果变量 `a_value` 中的值是 `None` 而不是 `Some`,那么 `Some(x)` 模式不能匹配。
模式有两种形式refutable可反驳的和 irrefutable不可反驳的。能匹配任何传递的可能值的模式被称为是**不可反驳的***irrefutable*)。一个例子就是 `let x = 5;` 语句中的 `x`,因为 `x` 可以匹配任何值所以不可能会失败。对某些可能的值进行匹配会失败的模式被称为是**可反驳的***refutable*)。一个这样的例子便是 `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)`。如你所见,这不能编译:
让我们看看一个尝试在 Rust 要求不可反驳模式的地方使用可反驳模式以及相反情况的例子。在示例 19-8 中,有一个 `let` 语句,不过模式被指定为可反驳模式 `Some(x)`。如你所见,这不能编译:
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-08/src/main.rs:here}}
```
<span class="caption">示例 18-8: 尝试在 `let` 中使用可反驳模式</span>
<span class="caption">示例 19-8: 尝试在 `let` 中使用可反驳模式</span>
如果 `some_option_value` 的值是 `None`,其不会成功匹配模式 `Some(x)`,表明这个模式是可反驳的。然而,因为 `let` 对于 `None` 匹配不能产生任何合法的代码,所以 `let` 语句只能接受不可反驳模式。Rust 会在编译时抱怨我们尝试在要求不可反驳模式的地方使用可反驳模式:
@ -26,21 +25,22 @@
因为我们没有覆盖(也不可能覆盖!)到模式 `Some(x)` 的每一个可能的值,所以 Rust 会合理地抗议。
为了修复在需要不可反驳模式的地方使用可反驳模式的情况,可以修改使用模式的代码:不同于使用 `let`,可以使用 `if let`。如此,如果模式不匹配,大括号中的代码将被忽略,其余代码保持有效。示例 18-9 展示了如何修复示例 18-8 中的代码。
为了修复在需要不可反驳模式的地方使用可反驳模式的情况,可以修改使用模式的代码:不同于使用 `let`,可以使用 `if let`。如此,如果模式不匹配,大括号中的代码将被忽略,其余代码保持有效。示例 19-9 展示了如何修复示例 19-8 中的代码。
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-09/src/main.rs:here}}
```
<span class="caption">示例 18-9: 使用 `if let` 和一个带有可反驳模式的代码块来代替 `let`</span>
<span class="caption">示例 19-9: 使用 `if let` 和一个带有可反驳模式的代码块来代替 `let`</span>
我们给了代码一个得以继续的出路!虽然我们没办法在避免产生错误的情况下使用不可反驳模式,但这段使用可反驳模式的代码是完全有效的。如果为 `if let` 提供了一个总是会匹配的模式,比如示例 18-10 中的 `x`,编译器会给出一个警告:
我们给代码留了一条后路!现在这段代码已经完全有效了。然而,如果我们给 `if let` 提供一个不可反驳模式(即总会匹配的模式),例如示例 19-10 中的 `x`,编译器就会给出警告:
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-10/src/main.rs:here}}
```
<span class="caption">示例 18-10: 尝试把不可反驳模式用到 `if let`</span>
<span class="caption">示例 19-10: 尝试把不可反驳模式用到 `if let`</span>
Rust 会抱怨将不可反驳模式用于 `if let` 是没有意义的:
@ -48,6 +48,6 @@ Rust 会抱怨将不可反驳模式用于 `if let` 是没有意义的:
{{#include ../listings/ch19-patterns-and-matching/listing-19-10/output.txt}}
```
基于此,`match`匹配分支必须使用可反驳模式除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。Rust 允许我们在只有一个匹配分支的`match`中使用不可反驳模式,不过这么做不是特别有用,并可以被更简单的 `let` 语句替代。
基于此,`match` 匹配分支必须使用可反驳模式除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。Rust 允许我们在只有一个匹配分支的`match` 中使用不可反驳模式,不过这么做不是特别有用,并可以被更简单的 `let` 语句替代。
目前我们已经讨论了所有可以使用模式的地方,以及可反驳模式与不可反驳模式的区别,下面让我们一起去把可以用来创建模式的语法过目一遍吧。

@ -1,8 +1,7 @@
## 所有的模式语法
## 模式语法
> [ch19-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch19-03-pattern-syntax.md)
> <br>
> commit 6fce661a0938aa0da06526e7b8f98fd7e67a222f
<!-- https://github.com/rust-lang/book/blob/main/src/ch19-03-pattern-syntax.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
在本节中,我们收集了模式中所有有效的语法,并讨论为什么以及何时你可能要使用这些语法。
@ -18,7 +17,7 @@
### 匹配命名变量
命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `match` 表达式时情况会有些复杂。因为 `match` 会开始一个新作用域,`match` 表达式中作为模式的一部分声明的变量会覆盖 `match` 结构之外的同名变量,与所有变量一样。在示例 18-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并在运行此代码或进一步阅读之前推断这段代码会打印什么。
命名变量Named variables是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而,当在 `match`、`if let` 或 `while let` 表达式中使用命名变量时,会出现一些复杂情况。由于这些表达式会开始一个新作用域,作为模式一部分在表达式内部声明的变量会遮蔽外部同名变量,这与所有变量的遮蔽规则一致。在示例 19-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并在运行此代码或进一步阅读之前推断这段代码会打印什么。
<span class="filename">文件名src/main.rs</span>
@ -26,21 +25,21 @@
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-11/src/main.rs:here}}
```
<span class="caption">示例 18-11: 一个 `match` 语句其中一个分支引入了覆盖变量 `y`</span>
<span class="caption">示例 19-11: 一个 `match` 语句其中一个分支引入了遮蔽变量 `y`</span>
让我们看看当 `match` 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 `x` 中定义的值,所以代码继续执行。
第二个匹配分支中的模式引入了一个新变量 `y`,它会匹配任何 `Some` 中的值。因为我们在 `match` 表达式的新作用域中,这是一个新变量,而不是开头声明为值 10 的那个 `y`。这个新的 `y` 绑定会匹配任何 `Some` 中的值,在这里是 `x` 中的值。因此这个 `y` 绑定了 `x``Some` 内部的值。这个值是 5所以这个分支的表达式将会执行并打印出 `Matched, y = 5`
如果 `x` 的值是 `None` 而不是 `Some(5)`,头两个分支的模式不会匹配,所以会匹配下划线。这个分支的模式中没有引入变量 `x`,所以此时表达式中的 `x` 会是外部没有被覆盖`x`。在这个假想的例子中,`match` 将会打印 `Default case, x = None`
如果 `x` 的值是 `None` 而不是 `Some(5)`,头两个分支的模式不会匹配,所以会匹配下划线。这个分支的模式中没有引入变量 `x`,所以此时表达式中的 `x` 会是外部没有被遮蔽`x`。在这个假想的例子中,`match` 将会打印 `Default case, x = None`
一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10`
为了创建能够比较外部 `x``y` 的值,而不引入覆盖变量`match` 表达式我们需要相应地使用带有条件的匹配守卫match guard。我们稍后将在 [“匹配守卫提供的额外条件”](#匹配守卫提供的额外条件) 这一小节讨论匹配守卫。
为了创建能够比较外部 `x``y` 的值,又不引入新的变量去遮蔽已有 `y` `match` 表达式我们需要相应地使用带有条件的匹配守卫match guard。我们稍后将在 [“匹配守卫提供的额外条件”](#匹配守卫提供的额外条件) 这一小节讨论匹配守卫。
### 多个模式
`match` 表达式中,可以使用 `|` 语法匹配多个模式,它代表 **或**_or_运算符模式。例如如下代码将 `x` 的值与匹配分支相比较,第一个分支有 **或** 选项,意味着如果 `x` 的值匹配此分支的任一个值,它就会运行:
`match` 表达式中,可以使用 `|` 语法匹配多个模式,它代表 **或**_or_运算符模式。例如如下代码将 `x` 的值与匹配分支相比较,第一个分支有**或**选项,意味着如果 `x` 的值匹配此分支的任一个值,它就会运行:
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-02-multiple-patterns/src/main.rs:here}}
@ -74,7 +73,7 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
#### 解构结构体
示例 18-12 展示带有两个字段 `x``y` 的结构体 `Point`,可以通过带有模式的 `let` 语句将其分解:
示例 19-12 展示带有两个字段 `x``y` 的结构体 `Point`,可以通过带有模式的 `let` 语句将其分解:
<span class="filename">文件名src/main.rs</span>
@ -82,9 +81,9 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-12/src/main.rs}}
```
<span class="caption">示例 18-12: 解构一个结构体的字段为单独的变量</span>
<span class="caption">示例 19-12: 解构一个结构体的字段为单独的变量</span>
这段代码创建了变量 `a``b` 来匹配结构体 `p` 中的 `x``y` 字段。这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 18-13 展示了与示例 18-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
这段代码创建了变量 `a``b` 来匹配结构体 `p` 中的 `x``y` 字段。这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 19-13 展示了与示例 19-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
<span class="filename">文件名src/main.rs</span>
@ -92,13 +91,13 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-13/src/main.rs}}
```
<span class="caption">示例 18-13: 使用结构体字段简写来解构结构体字段</span>
<span class="caption">示例 19-13: 使用结构体字段简写来解构结构体字段</span>
这段代码创建了变量 `x``y`,与变量 `p` 中的 `x``y` 相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。
也可以使用字面值作为结构体模式的一部分进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。
示例 19-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。
<span class="filename">文件名src/main.rs</span>
@ -106,7 +105,7 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-14/src/main.rs:here}}
```
<span class="caption">示例 18-14: 解构和匹配模式中的字面值</span>
<span class="caption">示例 19-14: 解构和匹配模式中的字面值</span>
第一个分支通过指定字段 `y` 匹配字面值 `0` 来匹配任何位于 `x` 轴上的点。此模式仍然创建了变量 `x` 以便在分支的代码中使用。
@ -118,7 +117,7 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
#### 解构枚举
本书之前曾经解构过枚举(例如第六章示例 6-5不过当时没有明确提到解构枚举的模式需要对应枚举所定义的储存数据的方式。让我们以示例 6-2 中的 `Message` 枚举为例,编写一个 `match` 使用模式解构每一个内部值,如示例 18-15 所示:
本书之前曾经解构过枚举(例如第六章示例 6-5不过当时没有明确提到解构枚举的模式需要对应枚举所定义的储存数据的方式。让我们以示例 6-2 中的 `Message` 枚举为例,编写一个 `match` 使用模式解构每一个内部值,如示例 19-15 所示:
<span class="filename">文件名src/main.rs</span>
@ -126,25 +125,25 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-15/src/main.rs}}
```
<span class="caption">示例 18-15: 解构包含不同类型值成员的枚举</span>
<span class="caption">示例 19-15: 解构包含不同类型值成员的枚举</span>
这段代码会打印出 `Change the color to red 0, green 160, and blue 255`。尝试改变 `msg` 的值来观察其他分支代码的运行。
对于像 `Message::Quit` 这样没有任何数据的枚举成员,不能进一步解构其值。只能匹配其字面值 `Message::Quit`,因此模式中没有任何变量。
对于像 `Message::Move` 这样的类结构体枚举成员,可以采用类似于匹配结构体的模式。在成员名称后,使用大括号并列出字段变量以便将其分解以供此分支的代码使用。这里使用了示例 18-13 所展示的简写。
对于像 `Message::Move` 这样的类结构体枚举成员,可以采用类似于匹配结构体的模式。在成员名称后,使用大括号并列出字段变量以便将其分解以供此分支的代码使用。这里使用了示例 19-13 所展示的简写。
对于像 `Message::Write` 这样的包含一个元素,以及像 `Message::ChangeColor` 这样包含三个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
#### 解构嵌套的结构体和枚举
目前为止,所有的例子都只匹配了深度为一级的结构体或枚举,不过当然也可以匹配嵌套的项!例如,我们可以重构列表 18-15 的代码在 `ChangeColor` 消息中同时支持 RGB 和 HSV 色彩模式,如示例 18-16 所示:
目前为止,所有的例子都只匹配了深度为一级的结构体或枚举,不过当然也可以匹配嵌套的项!例如,我们可以重构列表 19-15 的代码在 `ChangeColor` 消息中同时支持 RGB 和 HSV 色彩模式,如示例 19-16 所示:
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-16/src/main.rs}}
```
<span class="caption">示例 18-16: 匹配嵌套的枚举</span>
<span class="caption">示例 19-16: 匹配嵌套的枚举</span>
`match` 表达式第一个分支的模式匹配一个包含 `Color::Rgb` 枚举成员的 `Message::ChangeColor` 枚举成员,然后模式绑定了 3 个内部的 `i32` 值。第二个分支的模式也匹配一个 `Message::ChangeColor` 枚举成员,但是其内部的枚举会匹配 `Color::Hsv` 枚举成员。我们可以在一个 `match` 表达式中指定这些复杂条件,即使会涉及到两个枚举。
@ -166,7 +165,7 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
#### 使用 `_` 忽略整个值
我们已经使用过下划线作为匹配但不绑定任何值的通配符模式了。虽然这作为 `match` 表达式最后的分支特别有用,也可以将其用于任意模式,包括函数参数中,如示例 18-17 所示:
我们已经使用过下划线作为匹配但不绑定任何值的通配符模式了。虽然这作为 `match` 表达式最后的分支特别有用,也可以将其用于任意模式,包括函数参数中,如示例 19-17 所示:
<span class="filename">文件名src/main.rs</span>
@ -174,7 +173,7 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-17/src/main.rs}}
```
<span class="caption">示例 18-17: 在函数签名中使用 `_`</span>
<span class="caption">示例 19-17: 在函数签名中使用 `_`</span>
这段代码会完全忽略作为第一个参数传递的值 `3`,并会打印出 `This code only uses the y parameter: 4`
@ -182,31 +181,31 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
#### 使用嵌套的 `_` 忽略部分值
也可以在一个模式内部使用`_` 忽略部分值,例如,当只需要测试部分值但在期望运行的代码中没有用到其他部分时。示例 18-18 展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置,但是可以取消设置,也可以在当前未设置时为其提供设置。
也可以在一个模式内部使用`_` 忽略部分值,例如,当只需要测试部分值但在期望运行的代码中没有用到其他部分时。示例 19-18 展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置,但是可以取消设置,也可以在当前未设置时为其提供设置。
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-18/src/main.rs:here}}
```
<span class="caption">示例 18-18: 当不需要 `Some` 中的值时在模式内使用下划线来匹配 `Some` 成员</span>
<span class="caption">示例 19-18: 当不需要 `Some` 中的值时在模式内使用下划线来匹配 `Some` 成员</span>
这段代码会打印出 `Can't overwrite an existing customized value` 接着是 `setting is Some(5)`。在第一个匹配分支,我们不需要匹配或使用任一个 `Some` 成员中的值;重要的部分是需要测试 `setting_value``new_setting_value` 都为 `Some` 成员的情况。在这种情况,我们打印出为何不改变 `setting_value`,并且不会改变它。
对于所有其他情况(`setting_value` 或 `new_setting_value` 任一为 `None`),这由第二个分支的 `_` 模式体现,这时确实希望允许 `new_setting_value` 变为 `setting_value`
也可以在一个模式中的多处使用下划线来忽略特定值,如示例 18-19 所示,这里忽略了一个五元元组中的第二和第四个值:
也可以在一个模式中的多处使用下划线来忽略特定值,如示例 19-19 所示,这里忽略了一个五元元组中的第二和第四个值:
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-19/src/main.rs:here}}
```
<span class="caption">示例 18-19: 忽略元组的多个部分</span>
<span class="caption">示例 19-19: 忽略元组的多个部分</span>
这会打印出 `Some numbers: 2, 8, 32`,值 4 和 16 会被忽略。
#### 通过在名字前以一个 `_` 开头来忽略未使用的变量
如果你创建了一个变量却不在任何地方使用它Rust 通常会给你一个警告,因为未使用的变量可能会是个 bug。但是有时创建一个还未使用的变量是有用的比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。示例 18-20 中创建了两个未使用变量,不过当编译代码时只会得到其中一个的警告:
如果你创建了一个变量却不在任何地方使用它Rust 通常会给你一个警告,因为未使用的变量可能会是个 bug。但是有时创建一个还未使用的变量是有用的比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。示例 19-20 中创建了两个未使用变量,不过当编译代码时只会得到其中一个的警告:
<span class="filename">文件名src/main.rs</span>
@ -214,41 +213,41 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-20/src/main.rs}}
```
<span class="caption">示例 18-20: 以下划线开始变量名以便去掉未使用变量警告</span>
<span class="caption">示例 19-20: 以下划线开始变量名以便去掉未使用变量警告</span>
这里得到了警告说未使用变量 `y`,不过没有警告说使用 `_x`
注意,只使用 `_` 和使用以下划线开头的名称有些微妙的不同:比如 `_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定。为了展示这个区别的意义,示例 18-21 会产生一个错误。
注意,只使用 `_` 和使用以下划线开头的名称有些微妙的不同:比如 `_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定。为了展示这个区别的意义,示例 19-21 会产生一个错误。
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-21/src/main.rs:here}}
```
<span class="caption">示例 18-21: 以下划线开头的未使用变量仍然会绑定值,它可能会获取值的所有权</span>
<span class="caption">示例 19-21: 以下划线开头的未使用变量仍然会绑定值,它可能会获取值的所有权</span>
我们会得到一个错误,因为 `s` 的值仍然会移动进 `_s`,并阻止我们再次使用 `s`。然而只使用下划线本身,并不会绑定值。示例 18-22 能够无错编译,因为 `s` 没有被移动进 `_`
我们会得到一个错误,因为 `s` 的值仍然会移动进 `_s`,并阻止我们再次使用 `s`。然而只使用下划线本身,并不会绑定值。示例 19-22 能够无错编译,因为 `s` 没有被移动进 `_`
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-22/src/main.rs:here}}
```
<span class="caption">示例 18-22: 单独使用下划线不会绑定值</span>
<span class="caption">示例 19-22: 单独使用下划线不会绑定值</span>
上面的代码能很好的运行;因为没有把 `s` 绑定到任何变量;它没有被移动。
#### 用 `..` 忽略剩余值
对于有多个部分的值,可以使用 `..` 语法来只使用特定部分并忽略其它值,同时避免不得不每一个忽略值列出下划线。`..` 模式会忽略模式中剩余的任何没有显式匹配的值部分。在示例 18-23 中,有一个 `Point` 结构体存放了三维空间中的坐标。在 `match` 表达式中,我们希望只操作 `x` 坐标并忽略 `y``z` 字段的值:
对于有多个部分的值,可以使用 `..` 语法来只使用特定部分并忽略其它值,同时避免不得不每一个忽略值列出下划线。`..` 模式会忽略模式中剩余的任何没有显式匹配的值部分。在示例 19-23 中,有一个 `Point` 结构体存放了三维空间中的坐标。在 `match` 表达式中,我们希望只操作 `x` 坐标并忽略 `y``z` 字段的值:
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-23/src/main.rs:here}}
```
<span class="caption">示例 18-23: 通过使用 `..` 来忽略 `Point` 中除 `x` 以外的字段</span>
<span class="caption">示例 19-23: 通过使用 `..` 来忽略 `Point` 中除 `x` 以外的字段</span>
这里列出了 `x` 值,接着仅仅包含了 `..` 模式。这比不得不列出 `y: _``z: _` 要来得简单,特别是在处理有很多字段的结构体,但只涉及一到两个字段时的情形。
`..` 会扩展为所需要的值的数量。示例 18-24 展示了元组中 `..` 的应用:
`..` 会扩展为所需要的值的数量。示例 19-24 展示了元组中 `..` 的应用:
<span class="filename">文件名src/main.rs</span>
@ -256,11 +255,11 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-24/src/main.rs}}
```
<span class="caption">示例 18-24: 只匹配元组中的第一个和最后一个值并忽略掉所有其它值</span>
<span class="caption">示例 19-24: 只匹配元组中的第一个和最后一个值并忽略掉所有其它值</span>
这里用 `first``last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 例子,因此其不能编译:
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。示例 19-25 展示了一个带有歧义的 `..` 例子,因此其不能编译:
<span class="filename">文件名src/main.rs</span>
@ -268,7 +267,7 @@ Rust 知道 `'c'` 位于第一个模式的范围内,并会打印出 `early ASC
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-25/src/main.rs}}
```
<span class="caption">示例 18-25: 尝试以有歧义的方式运用 `..`</span>
<span class="caption">示例 19-25: 尝试以有歧义的方式运用 `..`</span>
如果编译上面的例子,会得到下面的错误:
@ -282,13 +281,13 @@ Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个
**匹配守卫**_match guard_是一个指定于 `match` 分支模式之后的额外 `if` 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。
这个条件可以使用模式中创建的变量。示例 18-26 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x % 2 == 0` (当 `x` 是偶数的时候为真)
这个条件可以使用模式中创建的变量。示例 19-26 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x % 2 == 0` (当 `x` 是偶数的时候为真)
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-26/src/main.rs:here}}
```
<span class="caption">示例 18-26: 在模式中加入匹配守卫</span>
<span class="caption">示例 19-26: 在模式中加入匹配守卫</span>
上例会打印出 `The number 4 is even`。当 `num` 与模式中第一个分支比较时,因为 `Some(4)` 匹配 `Some(x)` 所以可以匹配。接着匹配守卫检查 `x` 除以 `2` 的余数是否等于 `0`,因为它等于 `0`,所以第一个分支被选择。
@ -296,7 +295,7 @@ Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个
无法在模式中表达类似 `if x % 2 == 0` 的条件,所以通过匹配守卫提供了表达类似逻辑的能力。这种替代表达方式的缺点是,编译器不会尝试为包含匹配守卫的模式检查穷尽性。
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。示例 18-27 展示了如何使用匹配守卫修复这个问题。
在示例 19-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。示例 19-27 展示了如何使用匹配守卫修复这个问题。
<span class="filename">文件名src/main.rs</span>
@ -304,19 +303,19 @@ Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-27/src/main.rs}}
```
<span class="caption">示例 18-27: 使用匹配守卫来测试与外部变量的相等性</span>
<span class="caption">示例 19-27: 使用匹配守卫来测试与外部变量的相等性</span>
现在这会打印出 `Default case, x = Some(5)`。现在第二个匹配分支中的模式不会引入一个覆盖外部 `y` 的新变量 `y`,这意味着可以在匹配守卫中使用外部的 `y`。相比指定会覆盖外部 `y` 的模式 `Some(y)`,这里指定为 `Some(n)`。此新建的变量 `n` 并没有覆盖任何值,因为 `match` 外部没有变量 `n`
匹配守卫 `if n == y` 并不是一个模式所以没有引入新变量。这个 `y` **正是** 外部的 `y` 而不是新的覆盖变量 `y`,这样就可以通过比较 `n``y` 来表达寻找一个与外部 `y` 相同的值的概念了。
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用于所有的模式。示例 18-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用于所有的模式。示例 19-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-28/src/main.rs:here}}
```
<span class="caption">示例 18-28: 结合多个模式与匹配守卫</span>
<span class="caption">示例 19-28: 结合多个模式与匹配守卫</span>
这个匹配条件表明此分支值匹配 `x` 值为 `4`、`5` 或 `6` **同时** `y``true` 的情况。运行这段代码时会发生的是第一个分支的模式因 `x``4` 而匹配,不过匹配守卫 `if y` 为假,所以第一个分支不会被选择。代码移动到第二个分支,这会匹配,此程序会打印出 `no`。这是因为 `if` 条件作用于整个 `4 | 5 | 6` 模式,而不仅是最后的值 `6`。换句话说,匹配守卫与模式的优先级关系看起来像这样:
@ -334,13 +333,13 @@ Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个
### `@` 绑定
_at_ 运算符(`@`)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 18-29 展示了一个例子,这里我们希望测试 `Message::Hello``id` 字段是否位于 `3..=7` 范围内,同时也希望能将其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称。
_at_ 运算符(`@`)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 19-29 展示了一个例子,这里我们希望测试 `Message::Hello``id` 字段是否位于 `3..=7` 范围内,同时也希望能将其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称。
```rust
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-29/src/main.rs:here}}
```
<span class="caption">示例 18-29: 使用 `@` 在模式中绑定值的同时测试它</span>
<span class="caption">示例 19-29: 使用 `@` 在模式中绑定值的同时测试它</span>
上例会打印出 `Found an id in range: 5`。通过在 `3..=7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。

Loading…
Cancel
Save