check to ch18-03

pull/278/head
KaiserY 6 years ago
parent 5f8dd0c86f
commit d20d5ca289

@ -1,12 +1,12 @@
# 模式用来匹配值的结构
> [ch18-00-patterns.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-00-patterns.md)
> [ch18-00-patterns.md](https://github.com/rust-lang/book/blob/master/src/ch18-00-patterns.md)
> <br>
> commit 928790637fb32026643c855915b4b2fd9d5abff3
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。结合使用模式和 `match` 表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成:
- 字面
- 字面
- 解构的数组、枚举、结构体或者元组
- 变量
- 通配符
@ -14,12 +14,6 @@
这些部分描述了我们要处理的数据的形状,接着可以用其匹配值来决定程序是否拥有正确的数据来运行特定部分的代码。
<!-- I think we need a concise description of what we use patterns for here,
what they provide the programmer. Hopefully you can see what I've trying to do,
above! But I think you'll agree it's not quite right, can you have a whack, try
to give the reader that explanation? -->
<!-- We tweaked the wording a bit, how's this? /Carol -->
我们通过将一些值与模式相比较来使用它。如果模式匹配这些值,我们对值部分进行相应处理。回忆一下第六章讨论 `match` 表达式时像硬币分类器那样使用模式。如果数据符合这个形状,就可以使用这些命名的片段。如果不符合,与该模式相关的代码则不会运行。
本章是所有模式相关内容的参考。我们将涉及到使用模式的有效位置,*refutable* 与 *irrefutable* 模式的区别,和你可能会见到的不同类型的模式语法。在最后,你将会看到如何使用模式创建强大而简洁的代码。

@ -1,8 +1,8 @@
## 所有可能会用到模式的位置
> [ch18-01-all-the-places-for-patterns.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-01-all-the-places-for-patterns.md)
> [ch18-01-all-the-places-for-patterns.md](https://github.com/rust-lang/book/blob/master/src/ch18-01-all-the-places-for-patterns.md)
> <br>
> commit b1de391964190a0cec101ecfc86e05c9351af565
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
模式出现在 Rust 的很多地方。你已经在不经意间使用了很多模式!本部分是一个所有有效模式位置的参考。
@ -18,26 +18,18 @@ match VALUE {
}
```
`match` 表达式必须是 **穷尽***exhaustive*)的,意为 `match` 表达式所有可能的值都必须被考虑到。一个确保覆盖每个可能值的方法是在最后一个分支使用捕获所有的模式 —— 比如,一个匹配任何值的名称永远也不会失败,因此可以覆盖所有匹配剩下的情况。
`match` 表达式必须是 **穷尽***exhaustive*)的,意为 `match` 表达式所有可能的值都必须被考虑到。一个确保覆盖每个可能值的方法是在最后一个分支使用捕获所有的模式比如,一个匹配任何值的名称永远也不会失败,因此可以覆盖所有匹配剩下的情况。
有一个特定的模式 `_` 可以匹配所有情况,不过它从不绑定任何变量。这在例如希望忽略任何未指定值的情况很有用。本章之后会详细讲解
有一个特定的模式 `_` 可以匹配所有情况,不过它从不绑定任何变量。这在例如希望忽略任何未指定值的情况很有用。本章之后的 “在模式中忽略值” 部分会详细介绍 `_` 模式的更多细节
### `if let` 条件表达式
第六章讨论过了 `if let` 表达式,以及它是如何主要用于编写等同于只关心一个情况的 `match` 语句简写的。`if let` 可以对应一个可选的带有代码的 `else``if let` 中的模式不匹配时运行。
<!-- Can you say up front why we'd use this, and not just a match? I've just
added something here, not sure if it's right -->
<!-- The first sentence says why-- it's a shorter way to write a `match` when
there's only one case we care about. Can you elaborate on why that's not clear
or up front? /Carol -->
示例 18-1 展示了也可以组合并匹配 `if let`、`else if` 和 `else if let` 表达式。这相比 `match` 表达式一次只能将一个值与模式比较提供了更多灵活性;一系列 `if let`/`else if`/`else if let` 分支并不要求其条件相互关联。
示例 18-1 展示了也可以组合并匹配 `if let`、`else if` 和 `else if let` 表达式。这相比 `match` 表达式一次只能将一个值与模式比较提供了更多灵活性;一系列 `if let`、`else if`、`else if let` 分支并不要求其条件相互关联。
示例 18-1 中的代码展示了一系列针对不同条件的检查来决定背景颜色应该是什么。为了达到这个例子的目的,我们创建了硬编码值的变量,在真实程序中则可能由询问用户获得。
如果用户指定了中意的颜色,将使用其作为背景颜色。如果今天是星期二,背景颜色将是绿色。如果用户指定了他们的年龄字符串并能够成功将其解析为数字的话,我们将根据这个数字使用紫色或者橙色。最后,如果没有一个条件符合,背景颜色将是蓝色:
<span class="filename">文件名: src/main.rs</span>
```rust
@ -64,16 +56,14 @@ fn main() {
<span class="caption">示例 18-1: 结合 `if let`、`else if`、`else if let` 以及 `else`</span>
如果用户指定了中意的颜色,将使用其作为背景颜色。如果今天是星期二,背景颜色将是绿色。如果用户指定了他们的年龄字符串并能够成功将其解析为数字的话,我们将根据这个数字使用紫色或者橙色。最后,如果没有一个条件符合,背景颜色将是蓝色:
这个条件结构允许我们支持复杂的需求。使用这里硬编码的值,例子会打印出 `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` 表达式则检查了。如果去掉最后的 `else` 块而遗漏处理一些情况,编译器也不会警告这类可能的逻辑错误。
<!-- So what would happen, we'd just end up with a program that wasn't correct,
in the Rust sense? -->
<!-- Yes, we would have a logic bug. /Carol -->
### `while let` 条件循环
一个与 `if let` 结构类似的是 `while let` 条件循环,它允许只要模式匹配就一直进行 `while` 循环。示例 18-2 展示了一个使用 `while let` 的例子,它使用 vector 作为栈并以先进后出的方式打印出 vector 中的值:
@ -92,27 +82,14 @@ while let Some(top) = stack.pop() {
<span class="caption">列表 18-2: 使用 `while let` 循环只要 `stack.pop()` 返回 `Some` 就打印出其值</span>
<!-- Some lovely simple, but edifying, examples in this chapter!-->
这个例子会打印出 3、2 接着是 1。`pop` 方法取出 vector 的最后一个元素并返回 `Some(value)`。如果 vector 是空的,它返回 `None`。`while` 循环只要 `pop` 返回 `Some` 就会一直运行其块中的代码。一旦其返回 `None``while` 循环停止。我们可以使用 `while let` 来弹出栈中的每一个元素。
### `for` 循环
如同第三章所讲的,`for` 循环是 Rust 中最常见的循环结构,不过还没有讲到的是 `for` 可以获取一个模式。在 `for` 循环中,模式是 `for` 关键字直接跟随的值,正如 `for x in y` 中的 `x`
<!-- Can you check the line I added above? I think it'd help to point out the
pattern section of a for loop straight away -->
<!-- Yep, looks good! /Carol -->
示例 18-3 中展示了如何使用 `for` 循环来解构,或拆开一个元组作为 `for` 循环的一部分:
<!-- Liz: We've been using the word "destructure" throughout the book in
chapters 3, 4, 5, and 16. In chapter 3, in the "Grouping Values into Tuples"
section, we said "This is called *destructuring*, because it breaks the single
tuple into three parts.". So I don't think we need to define destructure again
in this chapter, but I've added a small parenthetical here in case the reader
forgets. /Carol -->
```rust
let v = vec!['a', 'b', 'c'];
@ -123,7 +100,7 @@ for (index, value) in v.iter().enumerate() {
<span class="caption">列表 18-3: 在 `for` 循环中使用模式来解构元组</span>
会打印出:
示例 18-3 的代码会打印出:
```text
a is at index 0
@ -131,7 +108,7 @@ b is at index 1
c is at index 2
```
这里使用 `enumerate` 方法适配一个迭代器来产生一个值和其在迭代器中的索引,他们位于一个元组中。第一个 `enumerate` 调用会产生元组 `(0, 'a')`。当这个值匹配模式 `(index, value)``index` 将会是 0 而 `value` 将会是 'a',并打印出第一行输出。
这里使用 `enumerate` 方法适配一个迭代器来产生一个值和其在迭代器中的索引,他们位于一个元组中。第一个 `enumerate` 调用会产生元组 `(0, 'a')`。当这个值匹配模式 `(index, value)``index` 将会是 0 而 `value` 将会是 `'a'`,并打印出第一行输出。
### `let` 语句
@ -159,14 +136,9 @@ let (x, y, z) = (1, 2, 3);
这里将一个元组与模式匹配。Rust 会比较值 `(1, 2, 3)` 与模式 `(x, y, z)` 并发现此值匹配这个模式。在这个例子中,将会把 `1` 绑定到 `x``2` 绑定到 `y` 并将 `3` 绑定到 `z`。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。
<!-- so if we have a pattern of four elements, say (w, x, y, z), but only three
values, would the values would not bind at all? -->
<!-- Either too many or too few elements in the pattern is a type error. I've
added a small example below to illustrate. /Carol -->
如果模式中元素的数量不匹配元组中元素的数量,则整个类型不匹配,并会得到一个编译时错误。例如,示例 18-5 展示了尝试用两个变量解构三个元素的元组,这是不行的:
```rust,ignore
```rust,ignore,does_not_compile
let (x, y) = (1, 2, 3);
```
@ -193,7 +165,7 @@ error[E0308]: mismatched types
```rust
fn foo(x: i32) {
// code goes here
// 代码
}
```
@ -216,8 +188,8 @@ fn main() {
<span class="caption">列表 18-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`
因为如第十三章所讲闭包类似于函数,也可以在闭包参数中使用模式。
因为如第十三章所讲闭包类似于函数,也可以在闭包参数列表中使用模式。
现在我们见过了很多使用模式的方式了,不过模式在每个使用它的地方并不以相同的方式工作;在一些地方,模式必须是 *irrefutable* 的,意味着他们必须匹配所提供的任何值。在另一些情况,他们则可以是 refutable 的。接下来让我们讨论这个。
现在我们见过了很多使用模式的方式了,不过模式在每个使用它的地方并不以相同的方式工作;在一些地方,模式必须是 *irrefutable* 的,意味着他们必须匹配所提供的任何值。在另一些情况,他们则可以是 refutable 的。接下来让我们讨论这概念

@ -1,18 +1,18 @@
## Refutability可反驳性: 模式是否会匹配失效
> [ch18-02-refutability.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-02-refutability.md)
> [ch18-02-refutability.md](https://github.com/rust-lang/book/blob/master/src/ch18-02-refutability.md)
> <br>
> commit 267f442fa1c637eab07b4eebb64a6dcd2c943a36
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
模式有两种形式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` 表达式被限制为只能接受可反驳的模式,因为根据定义他们意在处理可能的失败 ———— 条件表达式的功能就是根据成功或失败执行不同的操作。
`let` 语句、 函数参数和 `for` 循环只能接受不可反驳的模式,因为通过不匹配的值程序无法进行有意义的工作。`if let` 和 `while let` 表达式被限制为只能接受可反驳的模式,因为根据定义他们意在处理可能的失败条件表达式的功能就是根据成功或失败执行不同的操作。
通常无需担心可反驳和不可反驳模式的区别,不过确实需要熟悉可反驳性的概念,这样当在错误信息中看到时就知道如何应对。遇到这些情况,根据代码行为的意图,需要修改模式或者使用模式的结构。
让我们看看一个尝试在 Rust 要求不可反驳模式的地方使用可反驳模式以及相反情况的例子。在示例 18-8 中,有一个 `let` 语句,不过模式被指定为可反驳模式 `Some(x)`。如你所见,这会出现错误:
```rust,ignore
```rust,ignore,does_not_compile
let Some(x) = some_option_value;
```
@ -22,7 +22,7 @@ let Some(x) = some_option_value;
```text
error[E0005]: refutable pattern in local binding: `None` not covered
--> <anon>:3:5
-->
|
3 | let Some(x) = some_option_value;
| ^^^^^^^ pattern `None` not covered
@ -41,15 +41,9 @@ if let Some(x) = some_option_value {
<span class="caption">示例 18-9: 使用 `if let` 和一个带有可反驳模式的代码块来代替 `let`</span>
<!-- Whats the first commented out line here, I had though this was copied from
8-7 but it isn't quite the same -->
<!-- Sorry, that line has to do with the way we test our code examples and I
missed removing it before sending this chapter to you. Sorry about that! /Carol
-->
我们给了代码一个得以继续的出路!这段代码可以完美运行,当让如此意味着我们不能再使用不可反驳模式并免于收到错误。如果为 `if let` 提供了一个总是会匹配的模式,比如示例 18-10 中的 `x`,则会出错:
```rust,ignore
```rust,ignore,does_not_compile
if let x = 5 {
println!("{}", x);
};

@ -1,16 +1,10 @@
## 所有的模式语法
> [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-03-pattern-syntax.md)
> [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch18-03-pattern-syntax.md)
> <br>
> commit 3f91c488ad4261dee6a61db4f60c197074151aac
> commit bc6d44e5d2cc2ec291c3c93ee5a25b4a634a4403
通过本书我们已领略过许多不同类型模式的例子. 本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。
<!-- We don't always go over why we might want to use them for each section
here, presumably because it's clear why it's useful. I might recommend you do
just add a line to each, since we've promised it, and just to really hammer the
point home. Definitely keep it short and sweet though, where it's pretty clear.
-->
通过本书我们已领略过许多不同类型模式的例子。本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。
### 匹配字面值
@ -27,17 +21,13 @@ match x {
}
```
这段代码会打印 `one` 因为 `x` 的值是 1。
这段代码会打印 `one` 因为 `x` 的值是 1。这个语法用于代码得到某个具体值时进行操作。
### 匹配命名变量
<!-- I found this next bit a little tougher to follow, I've tried to clarify in
this opening paragraph, connect it all up, can you please check it? -->
<!-- Yep! Looks good! /Carol -->
命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `match` 表达式时情况会有些复杂。因为 `match` 会开始一个新作用域,`match` 表达式中作为模式的一部分声明的变量会覆盖 `match` 结构之外的同名变量 ———— 与所有变量一样。在示例 18-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并尝试在运行代码之前计算出会打印什么,或者继续阅读:
命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `match` 表达式时情况会有些复杂。因为 `match` 会开始一个新作用域,`match` 表达式中作为模式的一部分声明的变量会覆盖 `match` 结构之外的同名变量,与所有变量一样。在示例 18-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并尝试在运行代码之前计算出会打印什么,或者继续阅读:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
@ -60,26 +50,16 @@ fn main() {
第二个匹配分支中的模式引入了一个新变量 `y`,它会匹配任何 `Some` 中的值。因为我们在 `match` 表达式的新作用域中,这是一个新变量,而不是开头声明为值 10 的那个 `y`。这个新的 `y` 绑定会匹配任何 `Some` 中的值,在这里是 `x` 中的值。因此这个 `y` 绑定了 `x``Some` 内部的值。这个值是 5所以这个分支的表达式将会执行并打印出 `Matched, y = 5`
<!-- Below -- We haven't fully introduced the underscore yet, is there anything
else we could use for that final arm? -->
<!-- We have *used* the underscore briefly before, though-- we actually
introduced the underscore in chapter 6. There really isn't anything else that
we can put that will still have this example illustrating what we want to
illustrate. /Carol -->
如果 `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` 的值,而不引入覆盖变量的 `match` 表达式我们需要相应的使用带有条件的匹配守卫match guard。“匹配守卫提供的额外条件” 会讨论匹配守卫。
### 多个模式
`match` 表达式中,可以使用 `|` 语法匹配多个模式,它代表 **或***or*)的意思。例如,如下代码将 `x` 的值与匹配分支向比较,第一个分支有 **或** 选项,意味着如果 `x` 的值匹配此分支的任一个值,它就会运行:
<!-- I've tried to flesh this out a bit, can you check? -->
<!-- Yep, it's fine! /Carol -->
```rust
let x = 1;
@ -92,23 +72,10 @@ match x {
上面的代码会打印 `one or two`
<!-- Is there a corresponding "and" operator? Is that worth tacking on here? -->
<!-- No, there is not-- how could one value match, say, 1 AND 2? Does it make
sense why there isn't an "and" operator? /Carol -->
### 通过 `...` 匹配值的范围
`...` 语法允许你匹配一个闭区间范围内的值。在如下代码中,当模式匹配任何在此范围内的值时,该分支会执行:
<!-- Above--this seems like it's true, that the range allows you to match to
just one of the values? If so, can you say how this differs to using the or
operator? -->
<!-- I'm not sure what you mean by "match to just one of the values". `...`
matches any value between the two specified endpoints, which I thought would be
clear by the text below the code, and I changed "just one of" to "any of the
values within" above, and mentioned what the equivalent "or" pattern would look
like below. Does that clear it up? /Carol -->
```rust
let x = 5;
@ -122,9 +89,6 @@ match x {
范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 唯一知道范围是否为空的类型。
<!-- why, because they are the only types with inherent order? -->
<!-- Nope, I've added the explanation /Carol -->
如下是一个使用 `char` 类型值范围的例子:
```rust
@ -141,10 +105,6 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII
### 解构并分解值
<!-- I moved the definition of destructure earlier in the chapter, to when we
first use it -->
<!-- See my comment there; we first use destructure in chapter 3 /Carol -->
也可以使用模式来解构结构体、枚举、元组和引用,以便使用这些值的不同部分。让我们来分别看一看。
#### 解构结构体
@ -170,17 +130,9 @@ fn main() {
<span class="caption">示例 18-12: 解构一个结构体的字段为单独的变量</span>
<!-- I'm not sure I follow which part of this is the shorthand, what is it
shorthand for, and which syntax here counts as the shorthand? Can you slow this
down, talk it through a little more. Is the point of this section that we have
a shorthand for destructuring, or that we are able to destructure these items
with patterns at all? -->
<!-- I've reorganized this section to start with the non-shorthand instead, is
this clearer? /Carol -->
这段代码创建了变量 `a``b` 来匹配变量 `p` 中的 `x``y` 字段。这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。
这段代码创建了变量 `a``b` 来匹配变量 `p` 中的 `x``y` 字段。
这个例子展示了模式中的变量名不必与结构体中的字段名一致,不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 18-13 展示了与示例 18-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 18-13 展示了与示例 18-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
<span class="filename">文件名: src/main.rs</span>
@ -205,11 +157,7 @@ fn main() {
也可以在部分结构体模式中使用字面值进行结构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或其他的点:
<!-- I'm not sure what you mean by "inner parts of a value" -- that we aren't
matching a whole value but part of it? -->
<!-- I've reworded, is this version clearer? /Carol -->
示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。
<span class="filename">文件名: src/main.rs</span>
@ -232,7 +180,9 @@ fn main() {
<span class="caption">示例 18-14: 解构和匹配模式中的字面值</span>
第一个分支通过指定字段 `y` 匹配字面值 `0` 来匹配任何位于 `x` 轴上的点。此模式仍然创建了变量 `x` 以便在分支的代码中使用。类似的,第二个分支通过指定字段 `x` 匹配字面值 `0` 来匹配任何位于 `y` 轴上的点,并为字段 `y` 创建了变量 `y`。第三个分支没有指定任何字面值,所以其会匹配任何其他的 `Point` 并为 `x``y` 两个字段创建变量。
第一个分支通过指定字段 `y` 匹配字面值 `0` 来匹配任何位于 `x` 轴上的点。此模式仍然创建了变量 `x` 以便在分支的代码中使用。
类似的,第二个分支通过指定字段 `x` 匹配字面值 `0` 来匹配任何位于 `y` 轴上的点,并为字段 `y` 创建了变量 `y`。第三个分支没有指定任何字面值,所以其会匹配任何其他的 `Point` 并为 `x``y` 两个字段创建变量。
在这个例子中,值 `p` 因为其 `x` 包含 0 而匹配第二个分支,因此会打印出 `On the y axis at 7`
@ -287,17 +237,53 @@ fn main() {
对于像 `Message::Write` 这样的包含一个元素,以及像 `Message::ChangeColor` 这样包含两个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
#### 解构引用
#### 解构嵌套的结构体 & 枚举
目前为止,所有的例子都只匹配了深度为一级的结构体。当然也可以匹配嵌套的结构体!
我们可以重构上面的例子来同时支持 RGB 和 HSV 色彩模式:
```rust
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32)
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
当模式所匹配的值中包含引用时,需要解构引用之中的值,这可以通过在模式中指定 `&` 做到。这让我们得到一个包含引用所指向数据的变量,而不是包含引用的变量。
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!(
"Change the color to red {}, green {}, and blue {}",
r,
g,
b
)
},
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!(
"Change the color to hue {}, saturation {}, and value {}",
h,
s,
v
)
}
_ => ()
}
}
```
<!-- What does it mean, to separate the reference and the value, precisely? So
that we specify Rust use the value in place of the reference? And what does &
here do, tell Rust to follow the reference to the value itself, rather than
work on the reference?-->
<!-- Yes, pretty much. I've tried rewording, is this clearer? /Carol -->
#### 解构引用
这在迭代器遍历引用,不过我们需要使用闭包中的值而不是其引用时非常有用
当模式所匹配的值中包含引用时,需要解构引用之中的值,这可以通过在模式中指定 `&` 做到。这让我们得到一个包含引用所指向数据的变量,而不是包含引用的变量。这个技术在通过迭代器遍历引用时,我们需要使用闭包中的值而不是其引用时非常有用。
示例 18-16 中的例子遍历一个 vector 中的 `Point` 实例的引用,并同时解构引用和其中的结构体以方便对 `x``y` 值进行计算:
@ -321,9 +307,6 @@ let sum_of_squares: i32 = points
<span class="caption">示例 18-16: 将结构体的引用解构到其字段值中</span>
<!-- and what do we actually get, instead of the error? -->
<!-- Added explanation text below /Carol -->
这段代码的结果是变量 `sum_of_squares` 的值为 135这个结果是将 `points` vector 中每一个 `Point``x``y` 的平方相加后求和得到的数字。
如果没有在 `&Point { x, y }` 中包含 `&` 则会得到一个类型不匹配错误,因为这样 `iter` 会遍历 vector 中项的引用而不是值本身。这个错误看起来像这样:
@ -356,10 +339,6 @@ let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
这将复杂的类型分解成部分组件以便可以单独使用我们感兴趣的值。
<!-- Can you round up the destructuring section here before we move on. For
this bit, maybe say explicitly what this would be useful for -->
<!-- Done /Carol -->
通过模式解构是一个方便利用部分值片段的手段,比如结构体中每个单独字段的值。
### 忽略模式中的值
@ -384,21 +363,12 @@ fn main() {
<span class="caption">示例 18-17: 在函数签名中使用 `_`</span>
<!-- What is this doing exactly, can you help the reader out here? Are we
letting the function run without a parameter at all? I'm not sure the purpose
clear enough at the moment -->
<!-- Done /Carol -->
这段代码会完全忽略作为第一个参数传递的值 `3`,并会打印出 `This code only uses the y parameter: 4`
这段代码会完全忽略作为第一个参数传递的值3并会打印出 `This code only uses the y parameter: 4`。大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。
在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样。
大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样。
#### 使用嵌套的 `_` 忽略部分值
<!-- When would we want to do this? -->
<!-- Done, moved the explanation up and made the example have a bit more
motivation /Carol -->
当只需要测试部分值但在期望运行的代码部分中没有使用它们时,也可以在另一个模式内部使用 `_` 来只忽略部分值。示例 18-18 展示了负责从设置中获取一个值的代码。业务需求是用户不允许覆盖某个设置中已经存在的自定义配置,但是可以重设设置和在目前未设置时提供新的设置。
```rust
@ -423,14 +393,8 @@ println!("setting is {:?}", setting_value);
对于所有其他情况(`setting_value` 或 `new_setting_value` 任一为 `None`),这由第二个分支的 `_` 模式体现,这时确实希望允许 `new_setting_value` 变为 `setting_value`
<!-- So when we need to match but don't actually need the value, is that what
we're saying? -->
<!-- Yes /Carol -->
也可以在一个模式中的多处使用下划线来忽略特定值,如示例 18-19 所示,这里忽略了一个五元元组中的第二和第四个值:
我们也可以在一个模式中多处使用下划线, 在例18-17中我们将忽略掉一个五元元组中的第二和第四个值:
```rust
let numbers = (2, 4, 8, 16, 32);
@ -462,9 +426,9 @@ fn main() {
这里得到了警告说未使用变量 `y`,不过没有警告说未使用下划线开头的变量。
注意, 只使用`_`和使用以下划线开头的名称有些微妙的不同:比如 `_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定。为了展示这个区别的意义,示例 18-21 会产生一个错误。
注意, 只使用 `_` 和使用以下划线开头的名称有些微妙的不同:比如 `_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定。为了展示这个区别的意义,示例 18-21 会产生一个错误。
```rust,ignore
```rust,ignore,does_not_compile
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
@ -530,7 +494,7 @@ fn main() {
}
```
<span class="caption">示例 18-24: 用 `..` 匹配元组中的第一个和最后一个值并忽略掉所有其它值</span>
<span class="caption">示例 18-24: 只匹配元组中的第一个和最后一个值并忽略掉所有其它值</span>
这里用 `first``last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。
@ -538,7 +502,7 @@ fn main() {
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
```rust,ignore,does_not_compile
fn main() {
let numbers = (2, 4, 8, 16, 32);
@ -562,84 +526,13 @@ error: `..` can only be used once per tuple or tuple struct pattern
| ^^
```
Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个值,以及在之后忽略多少个值。这段代码可能表明我们意在忽略 2绑定 `second` 为 4接着忽略 8、16 和 32抑或是意在忽略 2 和 4绑定 `second` 为 8接着忽略 16 和 32以此类推。变量名 `second` 对于 Rust 来说并没有任何特殊意义,所以会得到编译错误,因为在这两个地方使用 `..` 是有歧义的。
### 使用 `ref``ref mut` 在模式中创建引用
这里我们将看到使用 `ref` 来创建引用这样值的所有权就不会移动到模式的变量中。通常当匹配模式时模式所引入的变量将绑定一个值。Rust 的所有权规则意味着这个值将被移动到 `match` 中,或者任何使用此模式的位置。示例 18-26 展示了一个带有变量的模式的例子,并接着在 `match` 之后使用这整个值。这会编译失败,因为值 `robot_name` 的一部分在第一个 `match` 分支时被移动到了模式的变量 `name` 中:
<!-- Can you lay out what is supposed to happen with this code, that doesn't
work? -->
<!-- Done /Carol -->
```rust,ignore
let robot_name = Some(String::from("Bors"));
match robot_name {
Some(name) => println!("Found a name: {}", name),
None => (),
}
println!("robot_name is: {:?}", robot_name);
```
<span class="caption">示例 18-26: 在匹配分支的模式中创建获取值所有权的变量</span>
这个例子会编译失败,因为当 `name` 绑定 `robot_name``Some` 中的值时,其被移动到了 `match` 中。因为 `robot_name` 的部分所有权被移动到了 `name` 中,就不再能够在 `match` 之后的 `println!` 中使用 `robot_name`,因为 `robot_name` 不再有所有权。
<!-- Above -- why will that make it fail, because the bind is then invalid? -->
<!-- Yes, I've clarified a bit /Carol -->
<!--Below -- Is this then the solution, introducing &? I assume so, because we
dont have & in the example above, but the connection isn't clear -->
<!-- No, the solution is introducing `ref`. I've clarified /Carol -->
为了修复这段代码,需要让 `Some(name)` 模式借用部分 `robot_name` 而不是获取其所有权。在模式之外,我们见过了使用 `&` 创建引用来借用值,所以可能会想到的解决方案是将 `Some(name)` 改为 `Some(&name)`
然而,在 “解构并分解值” 部分我们见过了模式中的 `&` 并不能 **创建** 引用,它会 **匹配** 值中已经存在的引用。因为 `&` 在模式中已经有其他意义,不能够使用 `&` 在模式中创建引用。
相对的,为了在模式中创建引用,可以在新变量前使用 `ref` 关键字,如示例 18-27 所示:
```rust
let robot_name = Some(String::from("Bors"));
match robot_name {
Some(ref name) => println!("Found a name: {}", name),
None => (),
}
println!("robot_name is: {:?}", robot_name);
```
<span class="caption">示例 18-27: 创建一个引用以便模式变量不会获取其所有权</span>
这个例子可以编译,因为 `robot_name``Some` 成员的值没有被移动到 `match` 中;`match` 值获取了 `robot_name` 中数据的引用而没有移动它。
为了能够修改模式中匹配的值需要创建可变引用,使用 `ref mut` 替代 `&mut`,类似于上面用 `ref` 替代 `&`:模式中的 `&mut` 用于匹配已经存在的可变引用,而不是新建一个。示例 18-28 展示了一个创建可变引用模式的例子:
```rust
let mut robot_name = Some(String::from("Bors"));
match robot_name {
Some(ref mut name) => *name = String::from("Another name"),
None => (),
}
println!("robot_name is: {:?}", robot_name);
```
<span class="caption">示例 18-28: 在模式中使用 `ref mut` 来创建一个值的可变引用</span>
上例可以编译并打印出 `robot_name is: Some("Another name")`。因为 `name` 是一个可变引用,我们需要在匹配分支代码中使用 `*` 运算符解引用以便能够修改它。
Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个值,以及在之后忽略多少个值。这段代码可能表明我们意在忽略 `2`,绑定 `second``4`,接着忽略 `8`、`16` 和 `32`;抑或是意在忽略 `2``4`,绑定 `second``8`,接着忽略 `16``32`,以此类推。变量名 `second` 对于 Rust 来说并没有任何特殊意义,所以会得到编译错误,因为在这两个地方使用 `..` 是有歧义的。
### 匹配守卫提供的额外条件
<!-- Can you give a full definition of a match guard here, and what we use it
for, before covering how to do it? -->
**匹配守卫***match guard*)是一个指定与 `match` 分支模式之后的额外 `if` 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。
这个条件可以使用模式中创建的变量。示例 18-29 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x < 5`
这个条件可以使用模式中创建的变量。示例 18-26 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x < 5`
```rust
let num = Some(4);
@ -651,25 +544,15 @@ match num {
}
```
<span class="caption">示例 18-29: 在模式中加入匹配守卫</span>
<span class="caption">例18-27: 往一个模式中加入匹配守卫</span>
<span class="caption">示例 18-26: 在模式中加入匹配守卫</span>
上例会打印出 `less than five: 4`。当 `num` 与模式中第一个分支比较时,因为 `Some(4)` 匹配 `Some(x)` 所以可以匹配。接着匹配守卫检查 `x` 值是否小于 5因为 4 小于 5所以第一个分支被选择。
上例会打印出 `less than five: 4`。当 `num` 与模式中第一个分支比较时,因为 `Some(4)` 匹配 `Some(x)` 所以可以匹配。接着匹配守卫检查 `x` 值是否小于 `5`,因为 `4` 小于 `5`,所以第一个分支被选择。
相反如果 `num``Some(10)`,因为 10 不小于 5 所以第一个分支的匹配守卫为假。接着 Rust 会前往第二个分支,这会匹配因为它没有匹配守卫所以会匹配任何 `Some` 成员。
无法在模式中表达 `if x < 5` 的条件,所以匹配守卫提供了表现此逻辑的能力。
<!-- I think we need this spelled out, can you say what it is the match guard
is doing here? I've had a guess above, but I think it needs your review! -->
<!-- Reviewed and tweaked a bit! /Carol -->
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。实例 18-30 展示了如何使用匹配守卫修复这个问题:
<!-- Can you check this above -- I've tried to paraphrase the final paragraph
from that section. -->
<!-- Checked and reworded a bit /Carol -->
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。实例 18-27 展示了如何使用匹配守卫修复这个问题:
<span class="filename">文件名: src/main.rs</span>
@ -688,27 +571,13 @@ fn main() {
}
```
<span class="caption">示例 18-30: 使用匹配守卫来测试与外部变量的相等性</span>
<span class="caption">示例 18-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` 相同的值的概念了。
<!-- Why is this one not introducing a new variable y but 18-10 was? Instead we
create a new variable n and then compare it to the outer y, is that it? In
which case, I'm not understanding how we get n from destructuring x, can you
lay this out?-->
<!-- I've elaborated a bit, does this clear it up? /Carol -->
也可以在匹配守卫中使用或运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用域所有的模式。示例 18-31 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 4、5 **和** 6即使这看起来好像 `if y` 只作用于 6
<!-- What's the match condition actually doing here, with y having a value of
`false`? Can you let us know how that's being applied to all the values in that
match arm? -->
<!-- The point of the example here is to illustrate operator precedence, that
this code might look like it's saying `4 | 5 | (6 if y)` but it's actually
saying `(4 | 5 | 6) if y`. I've tried to elaborate above and below, does that
make sense now? /Carol -->
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用域所有的模式。示例 18-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`
```rust
let x = 4;
@ -720,17 +589,9 @@ match x {
}
```
<span class="caption">示例 18-31: 结合多个模式与匹配守卫</span>
<span class="caption">示例 18-28: 结合多个模式与匹配守卫</span>
这个匹配条件表明此分支值匹配 `x` 值为 4、5 或 6 **同时** `y``true` 的情况。运行这段代码时会发生的是第一个分支的模式因 `x` 为 4 而匹配,不过匹配守卫 `if y` 为假,所以第一个分支不会被选择。代码移动到第二个分支,这会匹配,此程序会打印出 `no`
<!-- Is this what we mean, if 4 or 5 or 6 being equal to x is false, run the
first arm? And so, because it's applying that to all of the values (including
4), the second arm is run and not the first? -->
<!-- It seems like `if y` was confusing, I've tried to spell it out a bit more.
Does this make sense now? /Carol -->
这是因为 `if` 条件作用于整个 `4 | 5 | 6` 模式,而不仅是最后的值 `6`。换句话说,匹配守卫与模式的优先级关系看起来像这样:
这个匹配条件表明此分支值匹配 `x` 值为 `4`、`5` 或 `6` **同时** `y``true` 的情况。运行这段代码时会发生的是第一个分支的模式因 `x``4` 而匹配,不过匹配守卫 `if y` 为假,所以第一个分支不会被选择。代码移动到第二个分支,这会匹配,此程序会打印出 `no`。这是因为 `if` 条件作用于整个 `4 | 5 | 6` 模式,而不仅是最后的值 `6`。换句话说,匹配守卫与模式的优先级关系看起来像这样:
```text
(4 | 5 | 6) if y => ...
@ -746,14 +607,7 @@ Does this make sense now? /Carol -->
### `@` 绑定
<!-- Below - use @ to what, can you say explicitly what it does. Also what the
name of the operator is? -->
<!-- I don't think it has a name other than "the at operator". And we tried to
say what it does-- it creates a variable at the same time as letting us test
it, I've tried rewording a bit but I'm not sure why that wasn't explicit
enough, can you clarify if this still doesn't make sense? /Carol -->
at 运算符 `@` 允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 18-32 展示了一个例子,这里我们希望测试 `Message::Hello``id` 字段是否位于 `3...7` 范围内,同时也希望能其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称:
*at* 运算符(`@`)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 18-29 展示了一个例子,这里我们希望测试 `Message::Hello``id` 字段是否位于 `3...7` 范围内,同时也希望能其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称:
```rust
enum Message {
@ -775,7 +629,7 @@ match msg {
}
```
<span class="caption">示例 18-32: 使用 `@` 在模式中绑定值的同时测试它</span>
<span class="caption">示例 18-29: 使用 `@` 在模式中绑定值的同时测试它</span>
上例会打印出 `Found an id in range: 5`。通过在 `3...7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。
@ -785,6 +639,53 @@ match msg {
使用 `@` 可以在一个模式中同时测试和保存变量值。
### 遗留模式: `ref``ref mut`
在老版本的 Rust 中,`match` 会假设你希望移动匹配到的值。不过有时并不希望如此。例如:
```rust
let robot_name = &Some(String::from("Bors"));
match robot_name {
Some(name) => println!("Found a name: {}", name),
None => (),
}
println!("robot_name is: {:?}", robot_name);
```
这里 `robot_name` 是一个 `&Option<String>`。Rust 会抱怨 `Some(name)` 不匹配 `&Option<T>`,所以不得不这么写:
```rust,ignore
let robot_name = &Some(String::from("Bors"));
match robot_name {
&Some(name) => println!("Found a name: {}", name),
None => (),
}
println!("robot_name is: {:?}", robot_name);
```
接着 Rust 会说 `name` 尝试将 `String` 从 option 中移出,不过因为这是一个引用的 option所以是借用的因此不能被移动。这就是 `ref` 出场的地方:
```rust
let robot_name = &Some(String::from("Bors"));
match robot_name {
&Some(ref name) => println!("Found a name: {}", name),
None => (),
}
println!("robot_name is: {:?}", robot_name);
```
`ref` 关键字就像模式中 `&` 的对立面;它表明 “请将 `ref` 绑定到一个 `&String` 上,不要尝试移动”。换句话说,`&Some` 中的 `&` 匹配的是一个引用,而 `ref` **创建** 了一个引用。`ref mut` 类似 `ref` 不过对应的是可变引用。
无论如何,今天的 Rust 不再这样工作。如果尝试 `match` 某些借用的值,那么所有创建的绑定也都会尝试借用。这也意味着之前的代码也能正常工作。
因为 Rust 是后向兼容的backwards compatible所以不会移除 `ref``ref mut`,同时它们在一些不明确的场景还有用,比如希望可变地借用结构体的部分值而可变地借用另一部分的情况。你可能会在老的 Rust 代码中看到它们,所以请记住它们仍有价值。
## 总结
模式是 Rust 中一个很有用的功能,它帮助我们区分不同类型的数据。当用于 `match` 语句时Rust 确保模式会包含每一个可能的值,否则程序将不能编译。`let` 语句和函数参数的模式使得这些结构更强大,可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。

Loading…
Cancel
Save