|
|
|
@ -2,7 +2,7 @@
|
|
|
|
|
|
|
|
|
|
> [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch18-03-pattern-syntax.md)
|
|
|
|
|
> <br>
|
|
|
|
|
> commit bc6d44e5d2cc2ec291c3c93ee5a25b4a634a4403
|
|
|
|
|
> commit c231bf7e49446e78b147a814323d8f25013a605b
|
|
|
|
|
|
|
|
|
|
通过本书我们已领略过许多不同类型模式的例子。本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。
|
|
|
|
|
|
|
|
|
@ -25,7 +25,7 @@ match x {
|
|
|
|
|
|
|
|
|
|
### 匹配命名变量
|
|
|
|
|
|
|
|
|
|
命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `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">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -54,7 +54,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10`。
|
|
|
|
|
|
|
|
|
|
为了创建能够比较外部 `x` 和 `y` 的值,而不引入覆盖变量的 `match` 表达式,我们需要相应的使用带有条件的匹配守卫(match guard)。“匹配守卫提供的额外条件” 会讨论匹配守卫。
|
|
|
|
|
为了创建能够比较外部 `x` 和 `y` 的值,而不引入覆盖变量的 `match` 表达式,我们需要相应的使用带有条件的匹配守卫(match guard)。我们稍后将在“匹配守卫提供的额外条件” 这一小节讨论匹配守卫。
|
|
|
|
|
|
|
|
|
|
### 多个模式
|
|
|
|
|
|
|
|
|
@ -87,7 +87,7 @@ match x {
|
|
|
|
|
|
|
|
|
|
如果 `x` 是 1、2、3、4 或 5,第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1 ... 5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5`。相反指定范围就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候!
|
|
|
|
|
|
|
|
|
|
范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 唯一知道范围是否为空的类型。
|
|
|
|
|
范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 仅有的可以判断范围是否为空的类型。
|
|
|
|
|
|
|
|
|
|
如下是一个使用 `char` 类型值范围的例子:
|
|
|
|
|
|
|
|
|
@ -130,7 +130,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 18-12: 解构一个结构体的字段为单独的变量</span>
|
|
|
|
|
|
|
|
|
|
这段代码创建了变量 `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`:
|
|
|
|
|
|
|
|
|
@ -155,7 +155,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
这段代码创建了变量 `x` 和 `y`,与变量 `p` 中的 `x` 和 `y` 相匹配。其结果是变量 `x` 和 `y` 包含结构体 `p` 中的值。
|
|
|
|
|
|
|
|
|
|
也可以在部分结构体模式中使用字面值进行结构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
|
|
|
|
|
也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
|
|
|
|
|
|
|
|
|
|
示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。
|
|
|
|
|
|
|
|
|
@ -237,16 +237,16 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
对于像 `Message::Write` 这样的包含一个元素,以及像 `Message::ChangeColor` 这样包含两个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
|
|
|
|
|
|
|
|
|
|
#### 解构嵌套的结构体 & 枚举
|
|
|
|
|
#### 解构嵌套的结构体和枚举
|
|
|
|
|
|
|
|
|
|
目前为止,所有的例子都只匹配了深度为一级的结构体。当然也可以匹配嵌套的结构体!
|
|
|
|
|
目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。当然也可以匹配嵌套的项!
|
|
|
|
|
|
|
|
|
|
我们可以重构上面的例子来同时支持 RGB 和 HSV 色彩模式:
|
|
|
|
|
例如,我们可以重构列表 18-15 的代码来同时支持 RGB 和 HSV 色彩模式:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
enum Color {
|
|
|
|
|
Rgb(i32, i32, i32),
|
|
|
|
|
Hsv(i32, i32, i32)
|
|
|
|
|
Hsv(i32, i32, i32),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Message {
|
|
|
|
@ -266,7 +266,7 @@ fn main() {
|
|
|
|
|
r,
|
|
|
|
|
g,
|
|
|
|
|
b
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
Message::ChangeColor(Color::Hsv(h, s, v)) => {
|
|
|
|
|
println!(
|
|
|
|
@ -281,52 +281,13 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 解构引用
|
|
|
|
|
|
|
|
|
|
当模式所匹配的值中包含引用时,需要解构引用之中的值,这可以通过在模式中指定 `&` 做到。这让我们得到一个包含引用所指向数据的变量,而不是包含引用的变量。这个技术在通过迭代器遍历引用时,我们需要使用闭包中的值而不是其引用时非常有用。
|
|
|
|
|
|
|
|
|
|
示例 18-16 中的例子遍历一个 vector 中的 `Point` 实例的引用,并同时解构引用和其中的结构体以方便对 `x` 和 `y` 值进行计算:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# struct Point {
|
|
|
|
|
# x: i32,
|
|
|
|
|
# y: i32,
|
|
|
|
|
# }
|
|
|
|
|
#
|
|
|
|
|
let points = vec![
|
|
|
|
|
Point { x: 0, y: 0 },
|
|
|
|
|
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)
|
|
|
|
|
.sum();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 18-16: 将结构体的引用解构到其字段值中</span>
|
|
|
|
|
|
|
|
|
|
这段代码的结果是变量 `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)
|
|
|
|
|
| ^^^^^^^^^^^^ expected &Point, found struct `Point`
|
|
|
|
|
|
|
|
|
|
|
= note: expected type `&Point`
|
|
|
|
|
found type `Point`
|
|
|
|
|
```
|
|
|
|
|
<span class="caption">示例 18-16: 匹配嵌套的枚举</span>
|
|
|
|
|
|
|
|
|
|
这个错误表明 Rust 期望闭包匹配 `&Point`,不过我们尝试直接匹配 `Point` 值,而不是 `Point` 的引用。
|
|
|
|
|
`match` 表达式第一个分支的模式匹配一个包含 `Color::Rgb` 枚举成员的 `Message::ChangeColor` 枚举成员,然后模式绑定了3个内部的 `i32` 值。第二个分支的模式也匹配一个 `Message::ChangeColor` 枚举成员, 但是其内部的枚举会匹配 `Color::Hsv` 枚举成员。 我们可以在一个 `match` 表达式中指定这些复杂条件,即使会涉及到两个枚举。
|
|
|
|
|
|
|
|
|
|
#### 解构结构体和元组
|
|
|
|
|
|
|
|
|
|
甚至可以用复杂的方式来合成、匹配和嵌套解构模式。如下是一个负责结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来:
|
|
|
|
|
甚至可以用复杂的方式来混合、匹配和嵌套解构模式。如下是一个负责结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# struct Point {
|
|
|
|
@ -369,7 +330,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
#### 使用嵌套的 `_` 忽略部分值
|
|
|
|
|
|
|
|
|
|
当只需要测试部分值但在期望运行的代码部分中没有使用它们时,也可以在另一个模式内部使用 `_` 来只忽略部分值。示例 18-18 展示了负责从设置中获取一个值的代码。业务需求是用户不允许覆盖某个设置中已经存在的自定义配置,但是可以重设设置和在目前未设置时提供新的设置。
|
|
|
|
|
也可以在一个模式内部使用`_` 忽略部分值,例如,当只需要测试部分值但在期望运行的代码中没有用到其他部分时。示例 18-18 展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置,但是可以取消设置,也可以在当前未设置时为其提供设置。
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
let mut setting_value = Some(5);
|
|
|
|
@ -387,9 +348,9 @@ match (setting_value, new_setting_value) {
|
|
|
|
|
println!("setting is {:?}", setting_value);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">使用 18-18: 当不需要 `Some` 中的值时在模式内使用下划线来匹配 `Some` 成员</span>
|
|
|
|
|
<span class="caption">示例 18-18: 当不需要 `Some` 中的值时在模式内使用下划线来匹配 `Some` 成员</span>
|
|
|
|
|
|
|
|
|
|
这段代码会打印出 `Can't overwrite an existing customized value` 接着是 `setting is Some(5)`。在第一个匹配分支,我们不需要匹配或使用任一个 `Some` 成员中的值;重要的部分是需要测试 `setting_value` 和 `new_setting_value` 都为 `Some` 成员的情况。在这种情况,我们希望打印出为何不改变 `setting_value`,并且不会改变它。
|
|
|
|
|
这段代码会打印出 `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`。
|
|
|
|
|
|
|
|
|
@ -498,7 +459,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
这里用 `first` 和 `last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。
|
|
|
|
|
|
|
|
|
|
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的,Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 应用,因此其不能编译:
|
|
|
|
|
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的,Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 例子,因此其不能编译:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -530,7 +491,7 @@ Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个
|
|
|
|
|
|
|
|
|
|
### 匹配守卫提供的额外条件
|
|
|
|
|
|
|
|
|
|
**匹配守卫**(*match guard*)是一个指定与 `match` 分支模式之后的额外 `if` 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。
|
|
|
|
|
**匹配守卫**(*match guard*)是一个指定于 `match` 分支模式之后的额外 `if` 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。
|
|
|
|
|
|
|
|
|
|
这个条件可以使用模式中创建的变量。示例 18-26 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x < 5`:
|
|
|
|
|
|
|
|
|
@ -552,7 +513,7 @@ match num {
|
|
|
|
|
|
|
|
|
|
无法在模式中表达 `if x < 5` 的条件,所以匹配守卫提供了表现此逻辑的能力。
|
|
|
|
|
|
|
|
|
|
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。实例 18-27 展示了如何使用匹配守卫修复这个问题:
|
|
|
|
|
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。示例 18-27 展示了如何使用匹配守卫修复这个问题。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -575,9 +536,9 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
现在这会打印出 `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` 相同的值的概念了。
|
|
|
|
|
匹配守卫 `if n == y` 并不是一个模式所以没有引入新变量。这个 `y` **正是** 外部的 `y` 而不是新的覆盖变量 `y`,这样就可以通过比较 `n` 和 `y` 来表达寻找一个与外部 `y` 相同的值的概念了。
|
|
|
|
|
|
|
|
|
|
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用域所有的模式。示例 18-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`:
|
|
|
|
|
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用于所有的模式。示例 18-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
let x = 4;
|
|
|
|
@ -633,9 +594,9 @@ match msg {
|
|
|
|
|
|
|
|
|
|
上例会打印出 `Found an id in range: 5`。通过在 `3...7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。
|
|
|
|
|
|
|
|
|
|
第二个分支只在模式中指定了一个范围,分支相关代码代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值将会是 10、11 或 12,不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
|
|
|
|
|
第二个分支只在模式中指定了一个范围,分支相关代码代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值可以是 10、11 或 12,不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
|
|
|
|
|
|
|
|
|
|
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中不能像头两个分支那样对 `id` 字段的值进行任何测试:任何值都会匹配此分支。
|
|
|
|
|
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 `id` 字段的值进行测试:任何值都会匹配此分支。
|
|
|
|
|
|
|
|
|
|
使用 `@` 可以在一个模式中同时测试和保存变量值。
|
|
|
|
|
|
|
|
|
@ -690,4 +651,4 @@ println!("robot_name is: {:?}", robot_name);
|
|
|
|
|
|
|
|
|
|
模式是 Rust 中一个很有用的功能,它帮助我们区分不同类型的数据。当用于 `match` 语句时,Rust 确保模式会包含每一个可能的值,否则程序将不能编译。`let` 语句和函数参数的模式使得这些结构更强大,可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。
|
|
|
|
|
|
|
|
|
|
现在,作为本书的倒数第二个章节,让我们看看一些 Rust 众多功能中较为高级的部分。
|
|
|
|
|
接下来,在本书倒数第二章中,我们将介绍一些 Rust 众多功能中较为高级的部分。
|
|
|
|
|