pull/89/head
sunface 3 years ago
parent 260d5ce7aa
commit 4327fbf9f9

@ -33,7 +33,7 @@
- [模式匹配](basic/match-pattern/intro.md) - [模式匹配](basic/match-pattern/intro.md)
- [match和if let](basic/match-pattern/match-if-let.md) - [match和if let](basic/match-pattern/match-if-let.md)
- [解构Option](basic/match-pattern/option.md) - [解构Option](basic/match-pattern/option.md)
- [模式和匹配](basic/match-pattern/pattern-match.md) - [模式适用场景](basic/match-pattern/pattern-match.md)
- [全模式列表](basic/match-pattern/all-patterns.md) - [全模式列表](basic/match-pattern/all-patterns.md)
- [方法Method(todo)](basic/method.md) - [方法Method(todo)](basic/method.md)
- [泛型(todo)](basic/generitic.md) - [泛型(todo)](basic/generitic.md)

@ -244,6 +244,20 @@ Rust提供了一个非常遍历的方式让我们能生成连续的数值
5 5
``` ```
序列只允许用于数字或字符类型,因为编译器会在编译时检查序列不为空。字符和数字值是 Rust 仅有的可以判断范围是否为空的类型。
如下是一个使用字符类型序列的例子:
```rust
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
```
## 有理数和复数 ## 有理数和复数
Rust的标准库相比其它语言对于准入的门槛较高因此有理数和复数并未包含在标准库中: Rust的标准库相比其它语言对于准入的门槛较高因此有理数和复数并未包含在标准库中:

@ -1,11 +1,9 @@
# 全模式列表 # 全模式列表
在本书中我们已领略过许多不同类型模式的例子。在本节中,我们收集了模式中所有有效的语法,并讨论了为什么可能要使用每个语法 在本书中我们已领略过许多不同类型模式的例子. 本节的目标就是把这些模式语法都罗列出来,方便大家检索查阅
### 匹配字面值 ### 匹配字面值
如第六章所示,可以直接匹配字面值模式。如下代码给出了一些例子:
```rust ```rust
let x = 1; let x = 1;
@ -21,9 +19,7 @@ match x {
### 匹配命名变量 ### 匹配命名变量
命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `match` 表达式时情况会有些复杂。因为 `match` 会开始一个新作用域,`match` 表达式中作为模式的一部分声明的变量会覆盖 `match` 结构之外的同名变量,与所有变量一样。在示例 18-11 中,声明了一个值为 `Some(5)` 的变量 `x` 和一个值为 `10` 的变量 `y`。接着在值 `x` 上创建了一个 `match` 表达式。观察匹配分支中的模式和结尾的 `println!`,并在运行此代码或进一步阅读之前推断这段代码会打印什么。 在[match一章](./match-if-let#变量覆盖)中,我们有讲过变量覆盖的问题,这个在**匹配命名变量**时会遇到:
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
fn main() { fn main() {
@ -40,21 +36,19 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-11: 一个 `match` 语句其中一个分支引入了覆盖变量 `y`</span>
让我们看看当 `match` 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 `x` 中定义的值,所以代码继续执行。 让我们看看当 `match` 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 `x` 中定义的值,所以代码继续执行。
第二个匹配分支中的模式引入了一个新变量 `y`,它会匹配任何 `Some` 中的值。因为我们在 `match` 表达式的新作用域中,这是一个新变量,而不是开头声明为值 10 的那个 `y`。这个新的 `y` 绑定会匹配任何 `Some` 中的值,在这里是 `x` 中的值。因此这个 `y` 绑定了 `x``Some` 内部的值。这个值是 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` 会是外部没有被覆盖的 `x`在这个假想的例子中,`match` 将会打印 `Default case, x = None` 如果 `x` 的值是 `None` 而不是 `Some(5)`,头两个分支的模式不会匹配,所以会匹配模式`_`。这个分支的模式中没有引入变量 `x`,所以此时表达式中的 `x` 会是外部没有被覆盖的 `x`
一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10` 一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10`
为了创建能够比较外部 `x``y` 的值,而不引入覆盖变量的 `match` 表达式我们需要相应地使用带有条件的匹配守卫match guard。我们稍后将在 [“匹配守卫提供的额外条件”](#extra-conditionals-with-match-guards) 这一小节讨论匹配守卫 如果你不想引入变量覆盖,那么需要使用匹配守卫(match guard)的方式,稍后在[匹配守卫提供的额外条件](#匹配守卫提供的额外条件)中会讲解
### 多模式 ### 单分支多模式
`match` 表达式中,可以使用 `|` 语法匹配多个模式,它代表 **或***or*的意思。例如,如下代码将 `x` 的值与匹配分支相比较,第一个分支有 **或** 选项,意味着如果 `x` 的值匹配此分支的任一个值,它就会运行: `match` 表达式中,可以使用 `|` 语法匹配多个模式,它代表 **或**的意思。例如,如下代码将 `x` 的值与匹配分支相比较,第一个分支有 **或** 选项,意味着如果 `x` 的值匹配此分支的任何一个模式,它就会运行:
```rust ```rust
let x = 1; let x = 1;
@ -68,9 +62,11 @@ match x {
上面的代码会打印 `one or two` 上面的代码会打印 `one or two`
### 通过 `..=` 匹配值的范围 ### 通过序列`..=` 匹配值的范围
`..=` 语法允许你匹配一个闭区间范围内的值。在如下代码中,当模式匹配任何在此范围内的值时,该分支会执行: 在[数值类型](../base-type/numbers#序列(Range))中我们有讲到一个序列语法,该语言不仅可以用循环中,还能用于匹配模式。
`..=` 语法允许你匹配一个闭区间序列内的值。在如下代码中,当模式匹配任何在此序列内的值时,该分支会执行:
```rust ```rust
let x = 5; let x = 5;
@ -81,11 +77,11 @@ match x {
} }
``` ```
如果 `x` 是 1、2、3、4 或 5第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1..=5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5`。相反指定范围就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候! 如果 `x` 是 1、2、3、4 或 5第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1..=5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5`。相反指定序列就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候!
范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 仅有的可以判断范围是否为空的类型。 序列只允许用于数字或字符类型,因为编译器会在编译时检查序列不为空。字符和数字值是 Rust 仅有的可以判断范围是否为空的类型。
如下是一个使用 `char` 类型值范围的例子: 如下是一个使用字符类型序列的例子:
```rust ```rust
let x = 'c'; let x = 'c';
@ -97,17 +93,15 @@ match x {
} }
``` ```
Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII letter` Rust 知道 `c` 位于第一个模式的序列内,并会打印出 `early ASCII letter`
### 解构并分解值 ### 解构并分解值
也可以使用模式来解构结构体、枚举、元组和引用,以便使用这些值的不同部分。让我们来分别看一看 也可以使用模式来解构结构体、枚举、元组和引用。
#### 解构结构体 #### 解构结构体
示例 18-12 展示带有两个字段 `x``y` 的结构体 `Point`,可以通过带有模式的 `let` 语句将其分解: 下面代码展示了如何用`let`解构一个带有两个字段 `x``y` 的结构体 `Point`
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
struct Point { struct Point {
@ -124,13 +118,9 @@ 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` 因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。下例与上例有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
struct Point { struct Point {
@ -147,16 +137,12 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-13: 使用结构体字段简写来解构结构体字段</span>
这段代码创建了变量 `x``y`,与变量 `p` 中的 `x``y` 相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。 这段代码创建了变量 `x``y`,与结构体`p` 中的 `x``y`字段相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。
也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。 也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。 下文展示了固定某个字段的匹配方式:
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
# struct Point { # struct Point {
# x: i32, # x: i32,
@ -174,19 +160,16 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-14: 解构和匹配模式中的字面值</span> 首先是`match`第一个分支,指定匹配`y`为`0`的`Point`
然后第二个分支在第一个分支之后,匹配`y`不为`0``x`为`0`的`Point`;
最后一个分支匹配`x`不为`0``y`也不为`0`的`Point`.
第一个分支通过指定字段 `y` 匹配字面值 `0` 来匹配任何位于 `x` 轴上的点。此模式仍然创建了变量 `x` 以便在分支的代码中使用。
类似的,第二个分支通过指定字段 `x` 匹配字面值 `0` 来匹配任何位于 `y` 轴上的点,并为字段 `y` 创建了变量 `y`。第三个分支没有指定任何字面值,所以其会匹配任何其他的 `Point` 并为 `x``y` 两个字段创建变量。
在这个例子中,值 `p` 因为其 `x` 包含 0 而匹配第二个分支,因此会打印出 `On the y axis at 7` 在这个例子中,值 `p` 因为其 `x` 包含 0 而匹配第二个分支,因此会打印出 `On the y axis at 7`
#### 解构枚举 #### 解构枚举
本书之前的部分曾经解构过枚举,比如第六章中示例 6-5 中解构了一个 `Option<i32>`。一个当时没有明确提到的细节是解构枚举的模式需要对应枚举所定义的储存数据的方式。让我们以示例 6-2 中的 `Message` 枚举为例,编写一个 `match` 使用模式解构每一个内部值,如示例 18-15 所示: 下面代码以`Message`枚举为例,编写一个 `match` 使用模式解构每一个内部值:
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
enum Message { enum Message {
@ -223,21 +206,19 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-15: 解构包含不同类型值成员的枚举</span> 这里老生重提一句话,模式匹配一样要类型相同,因此匹配`Message::Move{1,2}`这样的枚举值,就必须要用`Message::Move{x,y}`这样的同类型模式才行。
这段代码会打印出 `Change the color to red 0, green 160, and blue 255`。尝试改变 `msg` 的值来观察其他分支代码的运行。 这段代码会打印出 `Change the color to red 0, green 160, and blue 255`。尝试改变 `msg` 的值来观察其他分支代码的运行。
对于像 `Message::Quit` 这样没有任何数据的枚举成员,不能进一步解构其值。只能匹配其字面值 `Message::Quit`,因此模式中没有任何变量。 对于像 `Message::Quit` 这样没有任何数据的枚举成员,不能进一步解构其值。只能匹配其字面值 `Message::Quit`,因此模式中没有任何变量。
对于像 `Message::Move` 这样的类结构体枚举成员,可以采用类似于匹配结构体的模式。在成员名称后,使用大括号并列出字段变量以便将其分解以供此分支的代码使用。这里使用了示例 18-13 所展示的简写。 对于另外两个枚举成员,就用相同类型的模式去匹配出对应的值即可。
对于像 `Message::Write` 这样的包含一个元素,以及像 `Message::ChangeColor` 这样包含三个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
#### 解构嵌套的结构体和枚举 #### 解构嵌套的结构体和枚举
目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。当然也可以匹配嵌套的项! 目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。当然也可以匹配嵌套的项!
例如,我们可以重构列表 18-15 的代码来同时支持 RGB 和 HSV 色彩模式: 例如使用下面的代码来同时支持 RGB 和 HSV 色彩模式:
```rust ```rust
enum Color { enum Color {
@ -276,37 +257,31 @@ fn main() {
} }
} }
``` ```
`match`第一个分支的模式匹配一个`Message::ChangeColor`枚举成员,该枚举成员又包含了一个`Color::Rgb`的枚举成员最终绑定了3个内部的`i32`值。第二个,就交给亲爱的读者来思考完成。
<span class="caption">示例 18-16: 匹配嵌套的枚举</span>
`match` 表达式第一个分支的模式匹配一个包含 `Color::Rgb` 枚举成员的 `Message::ChangeColor` 枚举成员,然后模式绑定了 3 个内部的 `i32` 值。第二个分支的模式也匹配一个 `Message::ChangeColor` 枚举成员, 但是其内部的枚举会匹配 `Color::Hsv` 枚举成员。我们可以在一个 `match` 表达式中指定这些复杂条件,即使会涉及到两个枚举。
#### 解构结构体和元组 #### 解构结构体和元组
甚至可以用复杂的方式来混合、匹配和嵌套解构模式。如下是一个复杂结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来: 甚至可以用复杂的方式来混合、匹配和嵌套解构模式。如下是一个复杂结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来:
```rust ```rust
# struct Point { struct Point {
# x: i32, x: i32,
# y: i32, y: i32,
# } }
#
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 }); let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
``` ```
这将复杂的类型分解成部分组件以便可以单独使用我们感兴趣的值。 这种将复杂类型分解匹配的方式,可以让我们单独得到感兴趣的值。
通过模式解构是一个方便利用部分值片段的手段,比如结构体中每个单独字段的值。
### 忽略模式中的值 ### 忽略模式中的值
有时忽略模式中的一些值是有用的,比如 `match` 中最后捕获全部情况的分支实际上没有做任何事,但是它确实对所有剩余情况负责。有一些简单的方法可以忽略模式中全部或部分值:使用 `_` 模式(我们已经见过了),在另一个模式中使用 `_` 模式,使用一个以下划线开始的名称,或者使用 `..` 忽略所剩部分的值。让我们来分别探索如何以及为什么要这么做。 有时忽略模式中的一些值是很有用的,比如在`match`中的最后一个分支使用`_`模式匹配剩余的所有值。 你也可以在另一个模式中使用 `_` 模式,使用一个以下划线开始的名称,或者使用 `..` 忽略所剩部分的值。
#### 使用 `_` 忽略整个值 #### 使用 `_` 忽略整个值
我们已经使用过下划线(`_`)作为匹配但不绑定任何值的通配符模式了。虽然 `_` 模式作为 `match` 表达式最后的分支特别有用,也可以将其用于任意模式,包括函数参数中,如示例 18-17 所示 虽然 `_` 模式作为 `match` 表达式最后的分支特别有用,但是我们可以让它更有用。例如可以将其用于函数参数中
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
fn foo(_: i32, y: i32) { fn foo(_: i32, y: i32) {
@ -318,15 +293,13 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-17: 在函数签名中使用 `_`</span>
这段代码会完全忽略作为第一个参数传递的值 `3`,并会打印出 `This code only uses the y parameter: 4` 这段代码会完全忽略作为第一个参数传递的值 `3`,并会打印出 `This code only uses the y parameter: 4`
大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样。 大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。在一些情况下忽略函数参数会变得特别有用,比如实现特征时,当你需要特定类型签名但是函数实现并不需要某个参数时。此时编译器就**不会警告说存在未使用的函数参数**,就跟使用命名参数一样。
#### 使用嵌套的 `_` 忽略部分值 #### 使用嵌套的 `_` 忽略部分值
可以在一个模式内部使用`_` 忽略部分值,例如,当只需要测试部分值但在期望运行的代码中没有用到其他部分时。示例 18-18 展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置,但是可以取消设置,也可以在当前未设置时为其提供设置。 可以在一个模式内部使用`_` 忽略部分值:
```rust ```rust
let mut setting_value = Some(5); let mut setting_value = Some(5);
@ -344,13 +317,14 @@ match (setting_value, new_setting_value) {
println!("setting is {:?}", setting_value); println!("setting is {:?}", setting_value);
``` ```
<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)`
对于所有其他情况(`setting_value` 或 `new_setting_value` 任一为 `None`),这由第二个分支的 `_` 模式体现,这时确实希望允许 `new_setting_value` 变为 `setting_value` 第一个匹配分支,我们不关心里面的值,只关心元组中两个元素的类型,因此对于`Some`中的值,直接进行忽略。
剩下的形如`(Some(_),None)``(None, Some(_))`, `(None,None)`形式,都由第二个分支`_`进行分配。
也可以在一个模式中的多处使用下划线来忽略特定值,如示例 18-19 所示,这里忽略了一个五元元组中的第二和第四个值:
还可以在一个模式中的多处使用下划线来忽略特定值,如下所示,这里忽略了一个五元元组中的第二和第四个值:
```rust ```rust
let numbers = (2, 4, 8, 16, 32); let numbers = (2, 4, 8, 16, 32);
@ -362,15 +336,13 @@ match numbers {
} }
``` ```
<span class="caption">示例 18-19: 忽略元组的多个部分</span> 老生常谈:模式匹配一定要类型相同,因此匹配`numbers`元组的模式,也必须有五个值。
这会打印出 `Some numbers: 2, 8, 32`, 值 4 和 16 会被忽略。 这会打印出 `Some numbers: 2, 8, 32`, 值 4 和 16 会被忽略。
#### 通过在名字前以一个下划线开头来忽略未使用的变量 #### 使用下划线开头忽略未使用的变量
如果你创建了一个变量却不在任何地方使用它, Rust 通常会给你一个警告,因为这可能会是个 bug。但是有时创建一个还未使用的变量是有用的比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。示例 18-20 中创建了两个未使用变量,不过当运行代码时只会得到其中一个的警告:
<span class="filename">文件名: src/main.rs</span> 如果你创建了一个变量却不在任何地方使用它, Rust 通常会给你一个警告,因为这可能会是个 bug。但是有时创建一个还未使用的变量是有用的比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头:
```rust ```rust
fn main() { fn main() {
@ -379,13 +351,11 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-20: 以下划线开始变量名以便去掉未使用变量警告</span> 这里得到了警告说未使用变量 `y`至于x则并没有警告。
这里得到了警告说未使用变量 `y`,不过没有警告说未使用下划线开头的变量。
注意, 只使用 `_` 和使用以下划线开头的名称有些微妙的不同:比如 `_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定。为了展示这个区别的意义,示例 18-21 会产生一个错误 注意, 只使用 `_` 和使用以下划线开头的名称有些微妙的不同:比如 **`_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定**。
```rust,ignore,does_not_compile ```rust
let s = Some(String::from("Hello!")); let s = Some(String::from("Hello!"));
if let Some(_s) = s { if let Some(_s) = s {
@ -394,10 +364,19 @@ if let Some(_s) = s {
println!("{:?}", s); println!("{:?}", s);
``` ```
`s`是一个拥有所有权的动态字符串,在上面代码中,我们会得到一个错误,因为 `s` 的值会被转移给 `_s` 在`println!`中再次使用`s`会报错:
```console
error[E0382]: borrow of partially moved value: `s`
--> src/main.rs:8:22
|
4 | if let Some(_s) = s {
| -- value partially moved here
...
8 | println!("{:?}", s);
| ^ value borrowed here after partial move
```
<span class="caption">示例 18-21: 以下划线开头的未使用变量仍然会绑定值,它可能会获取值的所有权</span> 只使用下划线本身,则并不会绑定值,因为 `s` 没有被移动进 `_`
我们会得到一个错误,因为 `s` 的值仍然会移动进 `_s`,并阻止我们再次使用 `s`。然而只使用下划线本身,并不会绑定值。示例 18-22 能够无错编译,因为 `s` 没有被移动进 `_`
```rust ```rust
let s = Some(String::from("Hello!")); let s = Some(String::from("Hello!"));
@ -409,13 +388,9 @@ if let Some(_) = s {
println!("{:?}", s); println!("{:?}", s);
``` ```
<span class="caption">示例 18-22: 单独使用下划线不会绑定值</span>
上面的代码能很好的运行;因为没有把 `s` 绑定到任何变量;它没有被移动。
#### 用 `..` 忽略剩余值 #### 用 `..` 忽略剩余值
对于有多个部分的值,可以使用 `..` 语法来只使用部分并忽略其它值,同时避免不得不每一个忽略值列出下划线。`..` 模式会忽略模式中剩余的任何没有显式匹配的值部分。在示例 18-23 中,有一个 `Point` 结构体存放了三维空间中的坐标。在 `match` 表达式中,我们希望只操作 `x` 坐标并忽略 `y``z` 字段的值: 对于有多个部分的值,可以使用 `..` 语法来只使用部分并忽略其它值,同时避免不得不每一个忽略值列出下划线。`..` 模式会忽略模式中剩余的任何没有显式匹配的值部分.
```rust ```rust
struct Point { struct Point {
@ -431,13 +406,9 @@ match origin {
} }
``` ```
<span class="caption">示例 18-23: 通过使用 `..` 来忽略 `Point` 中除 `x` 以外的字段</span> 这里列出了 `x` 值,接着使用了`..` 模式来忽略其它字段,这样的写法要比一一列出其它字段,然后用`_`忽略简洁的多。
这里列出了 `x` 值,接着仅仅包含了 `..` 模式。这比不得不列出 `y: _``z: _` 要来得简单,特别是在处理有很多字段的结构体,但只涉及一到两个字段时的情形。 还可以用`..`来忽略中间的所有值:
`..` 会扩展为所需要的值的数量。示例 18-24 展示了元组中 `..` 的应用:
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
fn main() { fn main() {
@ -451,15 +422,11 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-24: 只匹配元组中的第一个和最后一个值并忽略掉所有其它值</span>
这里用 `first``last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。 这里用 `first``last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。
然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。示例 18-25 展示了一个带有歧义的 `..` 例子,因此不能编译: 然而使用 `..` 必须是无歧义的。如果期望匹配和忽略的值是不明确的Rust 会报错。下面代码展示了一个带有歧义的 `..` 例子,因此不能编译:
<span class="filename">文件名: src/main.rs</span> ```rust
```rust,ignore,does_not_compile
fn main() { fn main() {
let numbers = (2, 4, 8, 16, 32); let numbers = (2, 4, 8, 16, 32);
@ -471,25 +438,28 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-25: 尝试以有歧义的方式运用 `..`</span>
如果编译上面的例子,会得到下面的错误: 如果编译上面的例子,会得到下面的错误:
```text ```text
error: `..` can only be used once per tuple or tuple struct pattern error: `..` can only be used once per tuple pattern // 每个元组模式只能使用一个`..'`
--> src/main.rs:5:22 --> src/main.rs:5:22
| |
5 | (.., second, ..) => { 5 | (.., second, ..) => {
| ^^ | -- ^^ can only be used once per tuple pattern
| |
| previously used here // 上一次使用在这里
error: could not compile `world_hello` due to previous error ^^
``` ```
Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个值,以及在之后忽略多少个值。这段代码可能表明我们意在忽略 `2`,绑定 `second``4`,接着忽略 `8`、`16` 和 `32`;抑或是意在忽略 `2``4`,绑定 `second``8`,接着忽略 `16``32`,以此类推。变量名 `second` 对于 Rust 来说并没有任何特殊意义,所以会得到编译错误,因为在这两个地方使用 `..` 是有歧义的。 Rust无法判断`second`应该匹配`numbers`中的第几个元素,因此在这里使用两个`..`模式,是由很大歧义的!
### 匹配守卫提供的额外条件 ### 匹配守卫提供的额外条件
**匹配守卫***match guard*)是一个指定于 `match` 分支模式之后的额外 `if` 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况 **匹配守卫***match guard*)是一个位于 `match` 分支模式之后的额外 `if` 条件,它能为分支模式提供更进一步的匹配条件
这个条件可以使用模式中创建的变量。示例 18-26 展示了一个 `match`,其中第一个分支有模式 `Some(x)` 还有匹配守卫 `if x < 5` 这个条件可以使用模式中创建的变量:
```rust ```rust
let num = Some(4); let num = Some(4);
@ -501,17 +471,13 @@ match num {
} }
``` ```
<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` 成员。 相反如果 `num``Some(10)`,因为 10 不小于 5 所以第一个分支的匹配守卫为假。接着 Rust 会前往第二个分支,这会匹配因为它没有匹配守卫所以会匹配任何 `Some` 成员。
无法在模式中表达 `if x < 5` 的条件,所以匹配守卫提供了表现此逻辑的能力。 因为模式中无法提供类如`if x < 5`
在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。示例 18-27 展示了如何使用匹配守卫修复这个问题。 在之前, 我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值,下面代码展示了如何使用匹配守卫修复这个问题。
<span class="filename">文件名: src/main.rs</span>
```rust ```rust
fn main() { fn main() {
@ -528,13 +494,11 @@ fn main() {
} }
``` ```
<span class="caption">示例 18-27: 使用匹配守卫来测试与外部变量的相等性</span>
现在这会打印出 `Default case, x = Some(5)`。现在第二个匹配分支中的模式不会引入一个覆盖外部 `y` 的新变量 `y`,这意味着可以在匹配守卫中使用外部的 `y`。相比指定会覆盖外部 `y` 的模式 `Some(y)`,这里指定为 `Some(n)`。此新建的变量 `n` 并没有覆盖任何值,因为 `match` 外部没有变量 `n` 现在这会打印出 `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` 也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,**同时匹配守卫的条件会作用于所有的模式**。下面代码展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`
```rust ```rust
let x = 4; let x = 4;
@ -546,15 +510,17 @@ match x {
} }
``` ```
<span class="caption">示例 18-28: 结合多个模式与匹配守卫</span> 这个匹配条件表明此分支值匹配 `x` 值为 `4`、`5` 或 `6` **同时** `y``true` 的情况。
虽然在第一个分支中,`x`匹配了模式`4`,但是对于匹配守卫`if y`来说,因为`y`是`false`,因此该守卫条件的值永远是`false`,也意味着第一个分支永远无法被匹配.
这个匹配条件表明此分支值匹配 `x` 值为 `4`、`5` 或 `6` **同时** `y``true` 的情况。运行这段代码时会发生的是第一个分支的模式因 `x``4` 而匹配,不过匹配守卫 `if y` 为假,所以第一个分支不会被选择。代码移动到第二个分支,这会匹配,此程序会打印出 `no`。这是因为 `if` 条件作用于整个 `4 | 5 | 6` 模式,而不仅是最后的值 `6`。换句话说,匹配守卫与模式的优先级关系看起来像这样: 下面的文字图解释了匹配守卫作用于多个模式时的优先级规则,第一张是正确的
```text ```text
(4 | 5 | 6) if y => ... (4 | 5 | 6) if y => ...
``` ```
不是: 第二张图是错误的
```text ```text
4 | 5 | (6 if y) => ... 4 | 5 | (6 if y) => ...
@ -564,7 +530,7 @@ match x {
### `@` 绑定 ### `@` 绑定
*at* 运算符(`@`)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 18-29 展示了一个例子,这里我们希望测试 `Message::Hello``id` 字段是否位于 `3..=7` 范围内,同时也希望能将其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称。 `@`(读作at)运算符允许为一个字段绑定另外一个变量。下面例子中,我们希望测试 `Message::Hello``id` 字段是否位于 `3..=7` 范围内,同时也希望能将其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称。
```rust ```rust
enum Message { enum Message {
@ -588,16 +554,12 @@ match msg {
<span class="caption">示例 18-29: 使用 `@` 在模式中绑定值的同时测试它</span> <span class="caption">示例 18-29: 使用 `@` 在模式中绑定值的同时测试它</span>
上例会打印出 `Found an id in range: 5`。通过在 `3..=7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式 上例会打印出 `Found an id in range: 5`。通过在 `3..=7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时将改值绑定到变量`id_variable`上
第二个分支只在模式中指定了一个范围,分支相关代码代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值可以是 10、11 或 12,不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。 第二个分支只在模式中指定了一个范围,`id` 字段的值可以是 `10、11 或 1`,不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 `id` 字段的值进行测试:任何值都会匹配此分支。 最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 `id` 字段的值进行测试:任何值都会匹配此分支。
使用 `@` 可以在一个模式中同时测试和保存变量值。 当你既想要限定分支范围,又想要使用分支的变量时,就可以用`@`来绑定到一个新的变量上,实现想要的功能。
## 总结
模式是 Rust 中一个很有用的功能,它帮助我们区分不同类型的数据。当用于 `match` 语句时Rust 确保模式会包含每一个可能的值,否则程序将不能编译。`let` 语句和函数参数的模式使得这些结构更强大,可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。
接下来,在本书倒数第二章中,我们将介绍一些 Rust 众多功能中较为高级的部分 至此,模式匹配的内容已经全部完结,复杂但是详尽,想要一次性全部记住属实不易,因此读者可以先留一个印象,等未来需要时,再来翻阅寻找具体的模式实现方式。

@ -1,4 +1,4 @@
# 模式和匹配 # 模式适用场景
## 模式 ## 模式
模式是Rust中的特殊语法它用来匹配类型中的结构和数据它往往和`match`表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成: 模式是Rust中的特殊语法它用来匹配类型中的结构和数据它往往和`match`表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:
@ -113,4 +113,21 @@ fn main() {
`&(3,5)`会匹配模式`&(x,y)`,因此`x`得到了`3``y`得到了`5`. `&(3,5)`会匹配模式`&(x,y)`,因此`x`得到了`3``y`得到了`5`.
#### #### if 和 if let
对于以下代码,编译器会报错:
```rust
let Some(x) = some_option_value;
```
因为右边的值可能为`None`,这种时候就不能进行匹配,也就是上面的代码遗漏了`None`的匹配。
类似`let`和`for`、`match`都必须要求完全覆盖匹配,才能通过编译。
但是对于`if let`,就可以这样使用:
```rust
if let Some(x) = some_option_value {
println!("{}", x);
}
```
因为`if let`允许匹配一种模式,而忽悠其余的模式。
Loading…
Cancel
Save