check to ch18-03

This commit is contained in:
KaiserY 2018-02-27 23:51:11 +08:00
parent 0929194552
commit bb90bbe996
2 changed files with 404 additions and 139 deletions

View File

@ -1,19 +1,24 @@
## Refutability(可反驳性): 模式是否会匹配失效
## Refutability(可反驳性): 模式是否会匹配失效
匹配模式有两种形式: refutable(可反驳)和irrefutable(不可反驳). 对任意可能的值进行匹配都不会失效的模式被称为是*irrefutable*(不可反驳)的, 而对某些可能的值进行匹配会失效的模式被称为是*refutable*(可反驳)的.
`let`语句、 函数参数和`for`循环被约束为只接受*irrefutable*模式, 因为如果模式匹配失效程序就不会正确运行. `if let`和`while let`表达式被约束为只接受*refutable*模式, 因为它们需要处理可能存在的匹配失效的情况, 并且如果模式匹配永不失效, 那它们就派不上用场了.
> [ch18-02-refutability.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-02-refutability.md)
> <br>
> commit 267f442fa1c637eab07b4eebb64a6dcd2c943a36
通常, 你不用关心*refutable*和*irrefutable*模式的区别, 当你看见它出现在了错误消息中时, 你只要了解*可反驳性*(refutability)的概念即可. 如果你得到一个涉及到可反驳性概念的错误消息, 根据你的代码行为的意图, 你只需改变匹配模式或者是改变你构造模式的方法即可.
模式有两种形式refutable可反驳的和 irrefutable不可反驳的。能匹配任何传递的可能值的模式被称为是 **不可反驳的***irrefutable*)。一个例子就是 `let x = 5;` 语句中的 `x`,因为 `x` 可以匹配任何值所以不可能会失败。对某些可能的值进行匹配会失败的模式被称为是 **可反驳的***refutable*)。一个这样的例子便是 `if let Some(x) = a_value` 表达式中的 `Some(x)`;如果变量 `a_value` 中的值是 `None` 而不是 `Some`,那么 `Some(x)` 模式不能匹配。
让我们来看几个例子. 在本章的前面部分, 我们提到`let x = 5;`. 这里`x`就是一个我们被允许使用*irrefutable*的模式: 因为它不可能匹配失效. 相反, 如果用`let`来匹配一个枚举的变体, 比如像**例18-7**中列出的那样从`Option<T>`枚举中只匹配`Some<T>`这个值:
`let` 语句、 函数参数和 `for` 循环只能接受不可反驳的模式,因为通过不匹配的值程序无法进行有意义的工作。`if let` 和 `while let` 表达式被限制为只能接受可反驳的模式,因为根据定义他们意在处理可能的失败 ———— 条件表达式的功能就是根据成功或失败执行不同的操作。
通常无需担心可反驳和不可反驳模式的区别,不过确实需要熟悉可反驳性的概念,这样当在错误信息中看到时就知道如何应对。遇到这些情况,根据代码行为的意图,需要修改模式或者使用模式的结构。
让我们看看一个尝试在 Rust 要求不可反驳模式的地方使用可反驳模式以及相反情况的例子。在示例 18-8 中,有一个 `let` 语句,不过模式被指定为可反驳模式 `Some(x)`。如你所见,这会出现错误:
```rust,ignore
let Some(x) = some_option_value;
```
<span class="caption">例18-7: 试试用一个有`let`的*refutable*模式</span>
<span class="caption">示例 18-8: 尝试在 `let` 中使用可反驳模式</span>
如果`some_option_value`的值是`None`, `some_option_value`将不会匹配模式`Some(x)`. 模式`Some(x)`是可反驳的(refutable), 因为存在一个使它匹配失效的值. 如果`some_option_value`的值是`None`, 那么`let`语句就不会产生任何效果. 因此Rust会在编译时会报*期望irrefutable模式但是却得到了一个refutable模式*的错误:
如果 `some_option_value` 的值是 `None`,其不会成功匹配模式 `Some(x)`,表明这个模式是可反驳的。然而 `let` 语句只能接受不可反驳模式因为代码不能通过 `None` 值进行有效的操作。Rust 会在编译时抱怨我们尝试在要求不可反驳模式的地方使用可反驳模式:
```text
error[E0005]: refutable pattern in local binding: `None` not covered
@ -23,9 +28,9 @@ error[E0005]: refutable pattern in local binding: `None` not covered
| ^^^^^^^ pattern `None` not covered
```
因为我们没有(也不能)覆盖到模式`Some(x)`的每一个可能的值, 所以Rust会报错.
因为我们没有(也不可能)覆盖到模式 `Some(x)` 的每一个可能的值, 所以 Rust 会合理的抗议.
如果我们采用*refutable*模式, 使用`if let`而不是`let`. 这样当模式不匹配时, 在花括号中的代码将不执行, 这段代码只有在值匹配模式的时候才会执行, 也只在此时才有意义. 例18-8显示了如何修正在例18-7中用`Some(x)`来匹配`some_option_value`的代码. 因为这个例子使用了`if let`, 因此使用*refutable*模式的`Some(x)`就没问题了:
为了修复在需要不可反驳模式的地方使用可反驳模式的情况,可以修改使用模式的代码:不同于使用 `let`,可以使用 `if let`。如此,如果模式不匹配,大括号中的代码将被忽略,其余代码保持有效。示例 18-9 展示了如何修复示例 18-8 中的代码。
```rust
# let some_option_value: Option<i32> = None;
@ -34,9 +39,15 @@ if let Some(x) = some_option_value {
}
```
<span class="caption">例18-8: 使用`if let`和一个有*refutable*模式的代码块来代替`let`</span>
<span class="caption">示例 18-9: 使用 `if let` 和一个带有可反驳模式的代码块来代替 `let`</span>
此外, 如果我们给`if let`一个绝对会匹配的*irrefutable*模式, 比如在例18-9中显示的`x`:
<!-- 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
if let x = 5 {
@ -44,9 +55,9 @@ if let x = 5 {
};
```
<span class="caption">例18-9: 尝试把一个*irrefutable*模式用到`if let`上</span>
<span class="caption">示例 18-10: 尝试把不可反驳模式用到 `if let`</span>
Rust将会抱怨把`if let`和一个*irrefutable*模式一起使用没有意义:
Rust 会抱怨将不可反驳模式用于 `if let` 是没有意义的:
```text
error[E0162]: irrefutable if-let pattern
@ -56,7 +67,6 @@ error[E0162]: irrefutable if-let pattern
| ^ irrefutable pattern
```
一般来说, 多数匹配使用*refutable*模式, 除非是那种可以匹配任意值的情况使用*irrefutable*模式. `match`操作符中如果只有一个*irrefutable*模式分支也没有什么问题, 但这就没什么特别的用处, 此时可以用一个更简单的`let`语句来替换. 不管是把表达式关联到`let`语句亦或是关联到只有一个*irrefutable*模式分支的`match`操作, 代码都肯定会运行, 如果它们的表达式一样的话最终的结果也相同.
目前我们已经讨论了所有可以使用模式的地方, 也介绍了*refutable*模式和*irrefutable*模式的不同, 下面让我们一起去把可以用来创建模式的语法过目一遍吧.
如此,匹配分支必须使用可反驳模式,除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。允许将不可反驳模式用于只有一个分支的 `match`,不过这么做不是特别有用,并可以被更简单的 `let` 语句替代。
目前我们已经讨论了所有可以使用模式的地方, 以及可反驳模式与不可反驳模式的区别,下面让我们一起去把可以用来创建模式的语法过目一遍吧。

View File

@ -1,10 +1,20 @@
## 所有的模式语法
通过本书我们已领略过一些不同类型模式的例子. 本节会列出所有在模式中有效的语法并且会阐述你为什么可能会用到它们中的每一个.
> [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch18-03-pattern-syntax.md)
> <br>
> commit 3f91c488ad4261dee6a61db4f60c197074151aac
### 字面量
通过本书我们已领略过许多不同类型模式的例子. 本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。
我们在第6章已经见过, 你可以直接匹配字面量:
<!-- 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.
-->
### 匹配字面值
如第六章所示,可以直接匹配字面值模式。如下代码给出了一些例子:
```rust
let x = 1;
@ -17,13 +27,15 @@ match x {
}
```
这段代码会打印`one`因为`x`的值是1.
这段代码会打印 `one` 因为 `x` 的值是 1。
### 命名变量
### 匹配命名变量
命名变量是可匹配任何值的`irrefutable`(不可反驳)模式.
<!-- 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`表达式会开启一个新的作用域. 在列表18-10中, 我们声明了一个值为`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>
@ -42,22 +54,31 @@ fn main() {
}
```
<span class="caption">列表18-10: 引入了一个阴影变量`y`的`match`语句</span>
<span class="caption">示例 18-11: 一个 `match` 语句其中一个分支引入了覆盖变量 `y`</span>
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
让我们看看当 `match` 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 `x` 中定义的值,所以继续。
让我们看看当`match`语句运行的时候发生了什么. 第一个匹配分支是模式`Some(50)`, `x`中的值(`Some(5)`)不匹配`Some(50)`, 所以我们继续. 在第二个匹配分支中, 模式`Some(y)`引入了一个可以匹配在`Some`里的任意值的新变量`y`. 因为我们位于`match`表达式里面的新作用域中, 所以`y`就是一个新变量而不是在开头被声明的其值为10的变量`y`. 这个新的`y`绑定将会匹配在`Some`中的任意值, 这里也就是`x`中的值, 因为`y`绑定到`Some`中的值是`x`, 这里是5, 所以我们就执行了这个分支中的表达式并打印出`Matched, y = 5`.
第二个匹配分支中的模式引入了一个新变量 `y`,它会匹配任何 `Some` 中的值。因为我们在 `match` 表达式的新作用域中,这是一个新变量,而不是开头声明为值 10 的那个 `y`。这个新的 `y` 绑定会撇配任何 `Some` 中的值,在这是是 `x` 中的值。因此这个 `y` 绑定了 `x``Some` 内部的值。这个值是 5所以这个分支的表达式将会执行并打印出 `Matched, y = 5`
如果`x`的值是`None`而不是`Some(5)`, 我们将会匹配下划线因为其它两个分支的模式将不会被匹配. 在这个匹配分支(下划线)的表达式里, 因为我们没有在分支的模式中引入变量`x`, 所以这个`x`仍然是`match`作用域外部的那个没被屏蔽的`x`. 在这个假想的例子中, `match`表达式将会打印出`Default case, x =
None`.
<!-- 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 -->
一旦`match`表达式执行完毕, 它的作用域也就结束了, 同时`match`内部的`y`也就结束了. 最后的`println!`会打印`at the end: x = Some(5), y = 10`.
如果 `x` 的值是 `None` 而不是 `Some(5)`,头两个分支的模式不会匹配,所以会匹配下划线。这个分支的模式中没有引入变量 `x`,所以此时表达式中的 `x` 会是外部没有被覆盖的 `x`。在这个假想的例子中,`match` 将会打印 `Default case, x = None`
为了让`match`表达式能比较外部变量`x`和`y`的值而不是内部引入的阴影变量`x`和`y`, 我们需要使用一个有条件的匹配守卫(guard). 我们将在本节的后面讨论匹配守卫.
一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10`
### 多种模式
为了创建能够比较外部 `x``y` 的值,而不引入覆盖变量的 `match` 表达式我们需要相应的使用带有条件的匹配守卫match guard。本部分的后面会讨论匹配守卫。
只有在`match`表达式中, 你可以通过`|`符号匹配多个模式, 它代表*或*(*or*)的意思:
### 多个模式
`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;
@ -69,11 +90,24 @@ match x {
}
```
上面的代码会打印`one or two`.
上面的代码会打印 `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;
@ -84,9 +118,14 @@ match x {
}
```
上面的代码中, 如果`x`是1、 2、 3、 4或5, 第一个分支就会匹配.
如果 `x` 1、2、3、4 5第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1 ... 5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5`。相反指定范围就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候!
范围只能是数字或`char`类型的值. 下面是一个使用`char`类型值范围的例子:
范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 唯一知道范围是否为空的类型。
<!-- why, because they are the only types with inherent order? -->
<!-- Nope, I've added the explanation /Carol -->
如下是一个使用 `char` 类型值范围的例子:
```rust
let x = 'c';
@ -98,34 +137,21 @@ match x {
}
```
上面的代码会打印`early ASCII letter`.
Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII letter`
### 解构并提取
### 解构并分解
模式可以用来*解构*(*destructure*)结构、枚举、元组和引用. 解构意味着把一个值分解成它的组成部分. 例18-11中的结构`Point`有两个字段`x`和`y`, 我们可以通过一个模式和`let`语句来进行提取:
<!-- 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 -->
<span class="filename">Filename: src/main.rs</span>
也可以使用模式来解构结构体、枚举、元组和引用,以便使用这些值的不同部分。让我们来分别看一看。
```rust
struct Point {
x: i32,
y: i32,
}
#### 解构结构体
fn main() {
let p = Point { x: 0, y: 7 };
示例 18-12 展示带有两个字段 `x``y` 的结构体 `Point`,可以通过带有模式的 `let` 语句将其分解:
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
```
<span class="caption">例18-11: 用结构的字段来解构</span>
上面的代码创建了匹配`p`中的`x`和`y`字段的变量`x`和`y`. 变量的名字必须匹配使用了这个写法中的字段. 如果我们想使用不同的变量名字, 我们可以在模式中使用`field_name: variable_name`. 在例18-12中, `a`会拥有`Point`实例的`x`字段的值, `b`会拥有`y`字段的值:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs
```rust
struct Point {
@ -142,9 +168,50 @@ fn main() {
}
```
<span class="caption">例18-12: 把结构解构到与字段不同名的变量中</span>
<span class="caption">示例 18-12: 解构一个结构体的字段为单独的变量</span>
为了测试和使用一个值内部的某个属性, 我们也可以用字面量来解构. 例18-13用一个`match`语句来判断一个点是位于`x`(此时`y` = 0)轴上还是在`y`(此时`x` = 0)轴上或者不在两个轴上面:
<!-- 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` 字段。
这个例子展示了模式中的变量名不必与结构体中的字段名一致,不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。示例 18-13 展示了与示例 18-12 有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
<span class="filename">文件名: src/main.rs</span>
```rust
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
```
<span class="caption">示例 18-13: 使用结构体字段简写来解构结构体字段</span>
这段代码创建了变量 `x``y`,与变量 `p` 中的 `x``y` 相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。
也可以在部分结构体模式中使用字面值进行结构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
示例 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 -->
<span class="filename">文件名: src/main.rs</span>
```rust
# struct Point {
@ -163,13 +230,76 @@ fn main() {
}
```
<span class="caption">例18-13: 解构和匹配一个模式中的字面量</span>
<span class="caption">示例 18-14: 解构和匹配模式中的字面值</span>
上面的代码会打印`On the y axis at 7`, 因为`p`的`x`字段的值是0, 这正好匹配第二个分支.
第一个分支通过指定字段 `y` 匹配字面值 `0` 来匹配任何位于 `x` 轴上的点。此模式仍然创建了变量 `x` 以便在分支的代码中使用。类似的,第二个分支通过指定字段 `x` 匹配字面值 `0` 来匹配任何位于 `y` 轴上的点,并为字段 `y` 创建了变量 `y`。第三个分支没有指定任何字面值,所以其会匹配任何其他的 `Point` 并为 `x``y` 两个字段创建变量。
第6章中我们对枚举进行了解构, 比如例6-5中, 我们用一个`match`表达式来解构一个`Option<i32>`, 其中被提取出来的一个值是`Some`内的变量.
这个例子中,值 `p` 因为其 `x` 包含 0 而匹配第二个分支,因此会打印出 `On the y axis at 7`
当我们正匹配的值在一个包含了引用的模式里面时, 为了把引用和值分割开我们可以在模式中指定一个`&`符号. 在迭代器对值的引用进行迭代时当我们想在闭包中使用值而不是引用的时侯这个符号在闭包里特别有用. 例18-14演示了如何在一个向量里迭代`Point`实例的引用, 为了能方便地对`x`和`y`的值进行计算还对引用的结构进行了解构:
#### 解构枚举
本书之前的部分曾经解构过么局,比如第六章中示例 6-5 中解构了一个 `Option<i32>`。一个当时没有明确提到的细节是解构枚举的模式需要对应枚举所定义的储存数据的方式。让我们以示例 6-2 中的 `Message` 枚举为例,编写一个 `match` 使用模式解构每一个内部值,如示例 18-15 所示:
<span class="filename">文件名: src/main.rs</span>
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.")
},
Message::Move { x, y } => {
println!(
"Move in the x direction {} and in the y direction {}",
x,
y
);
}
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => {
println!(
"Change the color to red {}, green {}, and blue {}",
r,
g,
b
)
}
}
}
```
<span class="caption">示例 18-15: 解构包含不同类型值成员的枚举</span>
这段代码会打印出 `Change the color to red 0, green 160, and blue 255`。尝试改变 `msg` 的值来观察其他分支代码的运行。
对于像 `Message::Quit` 这样没有任何数据的枚举成员,不能进一步解构其值。只能匹配其字面值 `Message::Quit`,因此模式中没有任何变量。
对于像 `Message::Move` 这样的类结构体枚举成员,可以采用类似于匹配结构体的模式。在成员名称后,使用大括号并列出字段变量以便将其分解以供此分支的代码使用。这里使用了示例 18-13 所真实的简写。
对于像 `Message::Write` 这样的包含一个元素,以及像 `Message::ChangeColor` 这样包含两个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
#### 解构引用
当模式所匹配的值中包含引用时,需要解构引用之中的值,这可以通过在模式中指定 `&` 做到。这让我们得到一个包含引用所指向数据的变量,而不是包含引用的变量。
<!-- 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` 值进行计算:
```rust
# struct Point {
@ -182,30 +312,38 @@ let points = vec![
Point { x: 1, y: 5 },
Point { x: 10, y: -3 },
];
let sum_of_squares: i32 = points
.iter()
.map(|&Point {x, y}| x * x + y * y)
.map(|&Point { x, y }| x * x + y * y)
.sum();
```
<span class="caption">例18-14: 把结构的引用解构到结构的字段值中</span>
<span class="caption">示例 18-16: 将结构体的引用解构到其字段值中</span>
因为`iter`会对向量里面的项目的引用进行迭代, 如果我们在`map`里的闭包的参数上忘了`&`符号, 我们将会得到下面的类型不匹配的错误:
<!-- 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 中项的引用而不是值本身。这个错误看起来像这样:
```text
error[E0308]: mismatched types
-->
|
14 | .map(|Point {x, y}| x * x + y * y)
14 | .map(|Point { x, y }| x * x + y * y)
| ^^^^^^^^^^^^ expected &Point, found struct `Point`
|
= note: expected type `&Point`
found type `Point`
```
这个报错提示Rust希望我们的闭包匹配参数匹配`&Point`, 但是我们却试图用一个`Point`的值的模式去匹配它, 而不是一个`Point`的引用.
这个错误表明 Rust 期望闭包匹配 `&Point`,不过我们尝试直接匹配 `Point` 值,而不是 `Point` 的引用。
我们可以用更复杂的方法来合成、匹配和嵌套解构模式: 下例中我们通过在一个元组中嵌套结构和元组来解构出所有的基础类型的值:
#### 解构结构体和元组
甚至可以用复杂的方式来合成、匹配和嵌套解构模式。如下是一个负责结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来:
```rust
# struct Point {
@ -216,42 +354,80 @@ error[E0308]: mismatched types
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 -->
通过模式解构是一个方便利用部分值片段的手段,比如结构体中每个单独字段的值。
### 忽略模式中的值
有一些简单的方法可以忽略模式中全部或部分值: 使用`_`模式, 在另一个模式中使用`_`模式, 使用一个以下划线开始的名字, 或者使用`..`来忽略掉所有剩下的值. 下面让我们来探索如何以及为什么要这么做.
有时忽略模式中的一些值是有用的,比如 `match` 中最后捕获全部情况的分支实际上没有做任何事,但是它确实对所有剩余情况负责。有一些简单的方法可以忽略模式中全部或部分值:使用 `_` 模式(我们已经见过了),在另一个模式中使用 `_` 模式,使用一个以下划线开始的名称,或者使用 `..` 忽略所剩部分的值。让我们来分别探索如何以及为什么要这么做。
#### 用`_`忽略整个值
#### 使 `_` 忽略整个值
我们已经见过了用下划线作为通配符会匹配任意值, 但是它不会绑定值. 把下划线模式用作`match`表达式的最后一个匹配分支特别有用, 我们可以在任意模式中使用它, 比如在例18-15中显示的函数参数:
我们已经使用过下划线作为匹配但不绑定任何值的通配符模式了。虽然下划线模式作为 `match` 表达式最后的分支特别有用,也可以将其用于任意模式,包括函数参数中,如示例 18-17 所示:
<span class="filename">文件名: src/main.rs</span>
```rust
fn foo(_: i32) {
// code goes here
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
fn main() {
foo(3, 4);
}
```
<span class="caption">例18-15: 在一个函数签名中使用`_`</span>
<span class="caption">示例 18-17: 在函数签名中使用 `_`</span>
通常, 你应该把这种函数的参数声明改成不用无用参数. 如果是要实现这样一个有特定类型签名的*trait*, 使用下划线可以让你忽略一个参数, 并且编译器不会像使用命名参数那样警告有未使用的函数参数.
<!-- 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`。大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。
我们也可以在另一个模式中使用`_`来忽略部分值. 在例18-16中, 第一个`match`分支中的模式匹配了一个`Some`值, 但是却通过下划线忽略掉了`Some`变量中的值:
在一些情况下忽略函数参数会变得特别有用,比如实现 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
let x = Some(5);
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match x {
Some(_) => println!("got a Some and I don't care what's inside"),
None => (),
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {:?}", setting_value);
```
<span class="caption">例18-16: 通过使用一个嵌套的下划线忽略`Some`变量中的值</span>
<span class="caption">使用 18-18: 当不需要 `Some` 中的值时在模式内使用下划线来匹配 `Some` 成员</span>
当代码关联的`match`分支不需要使用被嵌套的全部变量时这很有用.
这段代码会打印出 `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`
<!-- 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中我们将忽略掉一个五元元组中的第二和第四个值:
@ -265,13 +441,15 @@ match numbers {
}
```
<span class="caption">例18-17: 忽略元组中的多个部分</span>
<span class="caption">示例 18-19: 忽略元组的多个部分</span>
上面的代码将会打印出`Some numbers: 2, 8, 32`, 元组中的4和16会被忽略.
这会打印出 `Some numbers: 2, 8, 32`, 值 4 和 16 会被忽略。
#### 通过在名字前以一个下划线开头来忽略使用的变量
#### 通过在名字前以一个下划线开头来忽略使用的变量
如果你创建了一个变量却不使用它, Rust通常会给你一个警告, 因为这可能会是个bug. 如果你正在做原型或者刚开启一个项目, 那么你可能会创建一个暂时不用但是以后会使用的变量. 如果你面临这个情况并且希望Rust不要对你警告未使用的变量, 你可以让那个变量以一个下划线开头. 这和其它模式中的变量名没什么区别, 只是Rust不会警告你这个变量没用被使用. 在例18-18中, 我们会得到一个没用使用变量`y`的警告, 但是我们不会得到没用使用变量`_x`的警告:
如果你创建了一个变量却不在任何地方使用它, Rust 通常会给你一个警告,因为这可能会是个 bug。但是有时创建一个还未使用的变量是有用的比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。示例 18-20 中创建了两个未使用变量,不过当运行代码时只会得到其中一个的警告:
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
@ -280,11 +458,11 @@ fn main() {
}
```
<span class="caption">例18-18: 为了消除对未被使用变量的警告以一个下划线开始来命名变量</span>
<span class="caption">示例 18-20: 以下划线开始变量名以便去掉未使用变量警告</span>
注意, 只使用`_`和使用一个以一个下划线起头的名字是有微妙的不同的: `_x`仍然会把值绑定到变量上但是`_`不会绑定值.
这里得到了警告说未使用变量 `y`,不过没有警告说未使用下划线开头的变量。
例18-19显示了这种区别的主要地方: `s`将仍然被转移到`_s`, 它会阻止我们继续使用`s`:
注意, 只使用`_`和使用以下划线开头的名称有些微妙的不同:比如 `_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定。为了展示这个区别的意义,示例 18-21 会产生一个错误。
```rust,ignore
let s = Some(String::from("Hello!"));
@ -296,9 +474,9 @@ if let Some(_s) = s {
println!("{:?}", s);
```
<span class="caption">例18-19: 以下划线起头的未被使用的变量仍然会绑定值, 它也会拥有值的所有权</span>
<span class="caption">示例 18-21: 以下划线开头的未使用变量仍然会绑定值,它可能会获取值的所有权</span>
只使用下划线本身却不会绑定值. 例18-20在编译时将不会报错, 因为`s`不会被转移到`_`:
我们会得到一个错误,因为 `s` 的值仍然会移动进 `_s`,并阻止我们再次使用 `s`。然而只使用下划线本身,并不会绑定值。示例 18-22 能够无错编译,因为 `s` 没有被移动进 `_`
```rust
let s = Some(String::from("Hello!"));
@ -310,14 +488,13 @@ if let Some(_) = s {
println!("{:?}", s);
```
<span class="caption">例18-20: 使用下划线不会绑定值</span>
<span class="caption">示例 18-22: 单独使用下划线不会绑定值</span>
上面的代码能很好的运行. 因为我们没有把`s`绑定到其它地方, 它没有被转移.
上面的代码能很好的运行;因为没有把 `s` 绑定到任何变量,它没有被移动。
#### 用`..`忽略剩余
#### 用 `..` 忽略剩余值
对于有多个字段的值而言, 我们可以只提取少数字段并使用`..`来代替下划线, 这就避免了用`_`把剩余的部分列出来的麻烦. `..`模式将忽略值中没有被精确匹配值中的其它部分. 在例18-21中, 我们有一个持有三维空间坐标的`Point`结构. 在`match`表达式里,
我们只想操作`x`坐标上的值并忽略`y`坐标和`z`坐标上的值:
对于有多个部分的值,可以使用 `..` 语法来只使用部分并忽略其它值,同时避免不得不每一个忽略值列出下划线。`..` 模式会忽略模式中剩余的任何没有显式匹配的值部分。在示例 18-23 中,有一个 `Point` 结构体存放了三维空间中的坐标。在 `match` 表达式中,我们希望只操作 `x` 坐标并忽略 `y``z` 字段的值:
```rust
struct Point {
@ -333,11 +510,13 @@ match origin {
}
```
<span class="caption">例18-21: 通过用`..`来忽略除了`x`以外的所有其它`Point`的字段</span>
<span class="caption">示例 18-23: 通过使用 `..` 来忽略 `Point` 中除 `x` 以外的字段</span>
使用`..`比列出`y: _`和`z: _`写起来更简单. 当一个结构有很多字段但却只需要使用少量字段时`..`模式就特别有用.
这里列出了 `x` 值,接着仅仅包含了 `..` 模式。这比不得不列出 `y: _``z: _` 要来得简单,特别是在处理有很多字段的结构体,但只涉及一到两个字段时的情形。
`..`将会囊括它能匹配的尽可能多的值. 例18-22显示了一个在元组中使用`..`的情况:
`..` 会扩展为所需要的值的数量。示例 18-24 展示了元组中 `..` 的应用:
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
@ -351,11 +530,13 @@ fn main() {
}
```
<span class="caption">例18-22: 用`..`匹配元组中的第一和最后一个值并忽略掉所有的其它值</span>
<span class="caption">示例 18-24: 用 `..` 匹配元组中的第一个和最后一个值并忽略掉所有其它值</span>
我们在这里用`first`和`last`来匹配了第一和最后一个值. `..`将匹配并忽略中间的所有其它值.
这里用 `first``last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。
然而使用`..`必须清晰明了. 例18-23中的代码就不是很清晰, Rust看不出哪些值时我们想匹配的, 也看不出哪些值是我们想忽略的:
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 应用,因此其不能编译:
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
fn main() {
@ -369,9 +550,9 @@ fn main() {
}
```
<span class="caption">例18-23: 尝试含混不清地使用`..`</span>
<span class="caption">示例 18-25: 尝试以有歧义的方式运用 `..`</span>
如果我们编译上面的例子, 我们会得到下面的错误:
如果编译上面的例子,会得到下面的错误:
```text
error: `..` can only be used once per tuple or tuple struct pattern
@ -381,11 +562,15 @@ error: `..` can only be used once per tuple or tuple struct pattern
| ^^
```
上面的代码中在一个值被匹配到`second`之前不可能知道元组中有多少值应该被忽略, 同样在`second`被匹配后也不知道应该有多少值被忽略. 我们可以忽略2, 把`second`绑定到4, 然后忽略8、16和32, 或者我们也可以忽略2和4, 把`second`绑定到8, 然后再忽略16和32. 对Rust而言, 变量名`second`并不意味着某个确定的值, 因为像这样在两个地方使用`..`是含混不清的, 所以我们就得到了一个编译错误.
Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个值,以及在之后忽略多少个值。这段代码可能表明我们意在忽略 2绑定 `second` 为 4接着忽略 8、16 和 32抑或是意在忽略 2 和 4绑定 `second` 为 8接着忽略 16 和 32以此类推。变量名 `second` 对于 Rust 来说并没有任何特殊意义,所以会得到编译错误,因为在这两个地方使用 `..` 是有歧义的。
### 用`ref`和`ref mut`在模式中创建引用
### 使 `ref` `ref mut` 在模式中创建引用
当你匹配一个模式时, 模式匹配的变量会被绑定到一个值. 也就是说你会把值转移进`match`(或者是其它你使用了模式的地方), 这是所有权规则的作用. 例18-24提供了一个例子:
这里我们将看到使用 `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"));
@ -398,11 +583,22 @@ match robot_name {
println!("robot_name is: {:?}", robot_name);
```
<span class="caption">例18-24: 在一个匹配分支模式里创建的变量会拥有值的所有权</span>
<span class="caption">示例 18-26: 在匹配分支的模式中创建获取值所有权的变量</span>
上例的代码不能编译通过, 因为`robot_name`中的值被转移到了`match`中的`Some`的值所绑定的`name`里了.
这个例子会编译失败,因为当 `name` 绑定 `robot_name``Some` 中的值时,其被移动到了 `match` 中。因为 `robot_name` 的部分所有权被移动到了 `name` 中,就不再能够在 `match` 之后的 `println!` 中使用 `robot_name`,因为 `robot_name` 不再有所有权。
在模式中使用`&`会匹配已存在的引用中的值, 我们在"解构并提取值"这一节中已经见过了. 如果你想创建一个引用来借用模式中变量的值, 可以在新变量名前使用`ref`关键字, 比如例18-25:
<!-- 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"));
@ -415,11 +611,11 @@ match robot_name {
println!("robot_name is: {:?}", robot_name);
```
<span class="caption">例18-25: 创建一个引用这样模式中的变量就不会拥有值的所有权</span>
<span class="caption">示例 18-27: 创建一个引用以便模式变量不会获取其所有权</span>
上例可以编译, 因为`robot_name`没有被转移到`Some(ref name)`匹配分支的`Some`变量中; 这个匹配分支只是持有`robot_name`中的数据, `robot_name`并没被转移.
这个例子可以编译,因为 `robot_name``Some` 成员的值没有被移动到 `match` 中;`match` 值获取了 `robot_name` 中数据的引用而没有移动它。
如果要创建一个可变引用, 可以像例18-26那样使用`ref mut`:
为了能够修改模式中匹配的值需要创建可变引用,使用 `ref mut` 替代 `&mut`,类似于上面用 `ref` 替代 `&`:模式中的 `&mut` 用于匹配已经存在的可变引用,而不是新建一个。示例 18-28 展示了一个创建可变引用模式的例子:
```rust
let mut robot_name = Some(String::from("Bors"));
@ -432,13 +628,18 @@ match robot_name {
println!("robot_name is: {:?}", robot_name);
```
<span class="caption">例18-26: 在模式中使用`ref mut`来创建一个值的可变引用</span>
<span class="caption">示例 18-28: 在模式中使用 `ref mut` 来创建一个值的可变引用</span>
上例可以编译并打印出`robot_name is: Some("Another name")`. 因为在匹配分支的代码中`name`是一个可变引用, 为了能够改变这个值, 我们需要用`*`操作符来对它解引用.
上例可以编译并打印出 `robot_name is: Some("Another name")`。因为 `name` 是一个可变引用,我们需要在匹配分支代码中使用 `*` 运算符解引用以便能够修改它。
### 用了匹配守卫的额外条件
### 匹配守卫提供的额外条件
你可以通过在模式后面指定一个额外的`if`条件来往匹配分支中引入*匹配守卫*(*match guards*). 这个条件可以使用模式中创建的变量. 例18-27中的`match`表达式的第一个匹配分支就有一个匹配守卫:
<!-- 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`
```rust
let num = Some(4);
@ -450,11 +651,27 @@ match num {
}
```
<span class="caption">示例 18-29: 在模式中加入匹配守卫</span>
<span class="caption">例18-27: 往一个模式中加入匹配守卫</span>
上例会打印`less than five: 4`. 如果把`num`换成`Some(7)`, 上例将会打印`7`. 匹配守卫让你能表达出模式不能给予你的更多的复杂的东西.
上例会打印`less than five: 4`。当 `num` 与模式中第一个分支比较时,因为 `Some(4)` 匹配 `Some(x)` 所以可以匹配。接着匹配守卫检查 `x` 值是否小于 5因为 4 小于 5所以第一个分支被选择。
在例18-10中, 我们见过了模式中的阴影变量, 当一个值等于`match`外部的变量时我们不能用模式来表达出这种情况. 例18-28演示了我们如何用一个匹配守卫来解决这个问题:
相反如果 `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 -->
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
@ -471,11 +688,27 @@ fn main() {
}
```
<span class="caption">例18-28: 用一个匹配守卫来测试与外部变量的相等性</span>
<span class="caption">示例 18-30: 使用匹配守卫来测试与外部变量的相等性</span>
上例会打印出`Default case, x = Some(5)`. 因为第二个匹配分支没有往模式中引入新变量`y`, 所以外部变量`y`就不会被遮掩, 这样我们就可以在匹配守卫中直接使用外部变量`y`. 我们还把`x`解构到了内部变量`n`中, 这样我们就可以在匹配守卫中比较`n`和`y`了.
现在这会打印出 `Default case, x = Some(5)`。现在第二个匹配分支中的模式不会引入一个覆盖外部 `y` 的新变量 `y`,这意味着可以在匹配守卫中使用外部的 `y`。相比指定会覆盖外部 `y` 的模式 `Some(y)`,这里指定为 `Some(n)`。此新建的变量 `n` 并没有覆盖任何值,因为 `match` 外部没有变量 `n`
如果你在由`|`指定的多模式中使用匹配守卫, 匹配守卫的条件就会应用到所有的模式上. 例18-29演示了在第一个匹配分支中的匹配守卫会在被匹配的全部三个模式的值上生效:
在匹配守卫 `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 -->
```rust
let x = 4;
@ -487,24 +720,40 @@ match x {
}
```
<span class="caption">例18-29: 用一个匹配守卫来合成多个模式</span>
<span class="caption">示例 18-31: 结合多个模式与匹配守卫</span>
上例会打印`no`因为条件`if`会应用到整个模式`4 | 5 |
6`上, 而不是只应用到最后一个值`6`上面. 换一种说法, 一个与模式关联的匹配守卫的优先级是:
这个匹配条件表明此分支值匹配 `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`。换句话说,匹配守卫与模式的优先级关系看起来像这样:
```text
(4 | 5 | 6) if y => ...
```
而不是:
而不是
```text
4 | 5 | (6 if y) => ...
```
### `@`绑定
可以通过运行代码时的情况看出这一点:如果匹配守卫只作用于由 `|` 运算符指定的值列表的最后一个值,这个分支就会匹配且程序会打印出 `yes`
为了既能测试一个模式的值又能创建一个绑定到值的变量, 我们可以使用`@`. 例18-30演示了在匹配分支中我们想测试一个`Message::Hello`的`id`字段是否位于`3...7`之间, 同时我们又想绑定这个值这样我们可以在代码中使用它:
### `@` 绑定
<!-- 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`,与字段同名,不过出于示例的目的这里选择了不同的名称:
```rust
enum Message {
@ -514,8 +763,8 @@ enum Message {
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id @ 3...7 } => {
println!("Found an id in range: {}", id)
Message::Hello { id: id_variable @ 3...7 } => {
println!("Found an id in range: {}", id_variable)
},
Message::Hello { id: 10...12 } => {
println!("Found an id in another range")
@ -526,12 +775,18 @@ match msg {
}
```
<span class="caption">例18-30: 在测试模式中的值的时候用`@`符号来绑定值</span>
<span class="caption">示例 18-32: 使用 `@` 在模式中绑定值的同时测试它</span>
上例会打印`Found an id in range: 5`. 通过在范围前指定`id @`, 我们就在测试模式的同时又捕获了匹配范围的值. 在第二个分支我们只有一个在模式中指定的范围, 与这个分支关联的代码就不知道`id`是10还是11或12, 因为我们没有把`id`的值保存在某个变量中: 我们只知道如果匹配分支代码被执行这个值与范围匹配. 在最后一个匹配分支中我们指定了一个无范围的变量, 这个值就可以用在分支代码中, 此时我们没有对这个值进行任何其它的测试. 在一个模式中使用`@`让我们可以测试模式中的值并把它保存在一个变量中.
上例会打印出 `Found an id in range: 5`。通过在 `3...7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。
第二个分支只在模式中指定了一个范围,分支相关代码代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值将会是 10、11 或 12不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中不能像头两个分支那样对 `id` 字段的值进行任何测试:任何值都会匹配此分支。
使用 `@` 可以在一个模式中同时测试和保存变量值。
## 总结
模式是Rust的一个很有用的特点, 它帮助区分不同类型的数据. 当被用在`match`语句中时, Rust确保你的模式覆盖了每个可能的值. 在`let`语句和函数参数中的模式使得这些构造更加强大, 这些模式在赋值给变量的同时可以把值解构成更小的部分.
模式是 Rust 中一个很有用的功能,它帮助我们区分不同类型的数据。当用于 `match` 语句时Rust 确保模式会包含每一个可能的值,否则程序将不能编译。`let` 语句和函数参数的模式使得这些结构更强大,可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。
现在让我们进入倒数第二章吧, 让我们看一下Rust的某些高级特性.
现在,作为本书的倒数第二个章节,让我们看看一些 Rust 众多功能中较为高级的部分。