From d20d5ca289734f128b2b94e588ff4c41bfaa30f0 Mon Sep 17 00:00:00 2001 From: KaiserY Date: Sat, 8 Dec 2018 21:07:05 +0800 Subject: [PATCH] check to ch18-03 --- src/ch18-00-patterns.md | 12 +- src/ch18-01-all-the-places-for-patterns.md | 56 +--- src/ch18-02-refutability.md | 18 +- src/ch18-03-pattern-syntax.md | 341 ++++++++------------- 4 files changed, 144 insertions(+), 283 deletions(-) diff --git a/src/ch18-00-patterns.md b/src/ch18-00-patterns.md index ae3d961..0b0432f 100644 --- a/src/ch18-00-patterns.md +++ b/src/ch18-00-patterns.md @@ -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) >
-> commit 928790637fb32026643c855915b4b2fd9d5abff3 +> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f 模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。结合使用模式和 `match` 表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成: -- 字面量 +- 字面值 - 解构的数组、枚举、结构体或者元组 - 变量 - 通配符 @@ -14,12 +14,6 @@ 这些部分描述了我们要处理的数据的形状,接着可以用其匹配值来决定程序是否拥有正确的数据来运行特定部分的代码。 - - - 我们通过将一些值与模式相比较来使用它。如果模式匹配这些值,我们对值部分进行相应处理。回忆一下第六章讨论 `match` 表达式时像硬币分类器那样使用模式。如果数据符合这个形状,就可以使用这些命名的片段。如果不符合,与该模式相关的代码则不会运行。 本章是所有模式相关内容的参考。我们将涉及到使用模式的有效位置,*refutable* 与 *irrefutable* 模式的区别,和你可能会见到的不同类型的模式语法。在最后,你将会看到如何使用模式创建强大而简洁的代码。 diff --git a/src/ch18-01-all-the-places-for-patterns.md b/src/ch18-01-all-the-places-for-patterns.md index 3d830d2..1cf8856 100644 --- a/src/ch18-01-all-the-places-for-patterns.md +++ b/src/ch18-01-all-the-places-for-patterns.md @@ -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) >
-> 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` 中的模式不匹配时运行。 - - - -示例 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 中的代码展示了一系列针对不同条件的检查来决定背景颜色应该是什么。为了达到这个例子的目的,我们创建了硬编码值的变量,在真实程序中则可能由询问用户获得。 -如果用户指定了中意的颜色,将使用其作为背景颜色。如果今天是星期二,背景颜色将是绿色。如果用户指定了他们的年龄字符串并能够成功将其解析为数字的话,我们将根据这个数字使用紫色或者橙色。最后,如果没有一个条件符合,背景颜色将是蓝色: - 文件名: src/main.rs ```rust @@ -64,16 +56,14 @@ fn main() { 示例 18-1: 结合 `if let`、`else if`、`else if let` 以及 `else` +如果用户指定了中意的颜色,将使用其作为背景颜色。如果今天是星期二,背景颜色将是绿色。如果用户指定了他们的年龄字符串并能够成功将其解析为数字的话,我们将根据这个数字使用紫色或者橙色。最后,如果没有一个条件符合,背景颜色将是蓝色: + 这个条件结构允许我们支持复杂的需求。使用这里硬编码的值,例子会打印出 `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` 块而遗漏处理一些情况,编译器也不会警告这类可能的逻辑错误。 - - - ### `while let` 条件循环 一个与 `if let` 结构类似的是 `while let` 条件循环,它允许只要模式匹配就一直进行 `while` 循环。示例 18-2 展示了一个使用 `while let` 的例子,它使用 vector 作为栈并以先进后出的方式打印出 vector 中的值: @@ -92,27 +82,14 @@ while let Some(top) = stack.pop() { 列表 18-2: 使用 `while let` 循环只要 `stack.pop()` 返回 `Some` 就打印出其值 - - 这个例子会打印出 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`。 - - - 示例 18-3 中展示了如何使用 `for` 循环来解构,或拆开一个元组作为 `for` 循环的一部分: - - ```rust let v = vec!['a', 'b', 'c']; @@ -123,7 +100,7 @@ for (index, value) in v.iter().enumerate() { 列表 18-3: 在 `for` 循环中使用模式来解构元组 -这会打印出: +示例 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`。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。 - - - 如果模式中元素的数量不匹配元组中元素的数量,则整个类型不匹配,并会得到一个编译时错误。例如,示例 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() { 列表 18-7: 一个在参数中解构元组的函数 -这会打印出 `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 的。接下来让我们讨论这两个概念。 diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index 8ab6f40..25330f2 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -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) >
-> 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 - --> :3:5 + --> | 3 | let Some(x) = some_option_value; | ^^^^^^^ pattern `None` not covered @@ -41,15 +41,9 @@ if let Some(x) = some_option_value { 示例 18-9: 使用 `if let` 和一个带有可反驳模式的代码块来代替 `let` - - - 我们给了代码一个得以继续的出路!这段代码可以完美运行,当让如此意味着我们不能再使用不可反驳模式并免于收到错误。如果为 `if let` 提供了一个总是会匹配的模式,比如示例 18-10 中的 `x`,则会出错: -```rust,ignore +```rust,ignore,does_not_compile if let x = 5 { println!("{}", x); }; diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index 8d71848..0a9de37 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -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) >
-> commit 3f91c488ad4261dee6a61db4f60c197074151aac +> commit bc6d44e5d2cc2ec291c3c93ee5a25b4a634a4403 -通过本书我们已领略过许多不同类型模式的例子. 本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。 - - +通过本书我们已领略过许多不同类型模式的例子。本节会统一列出所有在模式中有效的语法并且会阐述你为什么可能会希望使用其中的每一个。 ### 匹配字面值 @@ -27,17 +21,13 @@ match x { } ``` -这段代码会打印 `one` 因为 `x` 的值是 1。 +这段代码会打印 `one` 因为 `x` 的值是 1。这个语法用于代码得到某个具体值时进行操作。 ### 匹配命名变量 - - - -命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 `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!`,并尝试在运行代码之前计算出会打印什么,或者继续阅读: -Filename: src/main.rs +文件名: src/main.rs ```rust fn main() { @@ -60,26 +50,16 @@ fn main() { 第二个匹配分支中的模式引入了一个新变量 `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`。 一旦 `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` 的值匹配此分支的任一个值,它就会运行: - - - ```rust let x = 1; @@ -92,23 +72,10 @@ match x { 上面的代码会打印 `one or two`。 - - - ### 通过 `...` 匹配值的范围 `...` 语法允许你匹配一个闭区间范围内的值。在如下代码中,当模式匹配任何在此范围内的值时,该分支会执行: - - - ```rust let x = 5; @@ -122,9 +89,6 @@ match x { 范围只允许用于数字或 `char` 值,因为编译器会在编译时检查范围不为空。`char` 和 数字值是 Rust 唯一知道范围是否为空的类型。 - - - 如下是一个使用 `char` 类型值范围的例子: ```rust @@ -141,10 +105,6 @@ Rust 知道 `c` 位于第一个模式的范围内,并会打印出 `early ASCII ### 解构并分解值 - - - 也可以使用模式来解构结构体、枚举、元组和引用,以便使用这些值的不同部分。让我们来分别看一看。 #### 解构结构体 @@ -170,17 +130,9 @@ fn main() { 示例 18-12: 解构一个结构体的字段为单独的变量 - - +这段代码创建了变量 `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`: 文件名: src/main.rs @@ -205,11 +157,7 @@ fn main() { 也可以在部分结构体模式中使用字面值进行结构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。 -示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或其他的点: - - - +示例 18-14 展示了一个 `match` 语句将 `Point` 值分成了三种情况:直接位于 `x` 轴上(此时 `y = 0` 为真)、位于 `y` 轴上(`x = 0`)或不在任何轴上的点。 文件名: src/main.rs @@ -232,7 +180,9 @@ fn main() { 示例 18-14: 解构和匹配模式中的字面值 -第一个分支通过指定字段 `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 + ) + } + _ => () + } +} +``` - - +#### 解构引用 -这在迭代器遍历引用,不过我们需要使用闭包中的值而不是其引用时非常有用 +当模式所匹配的值中包含引用时,需要解构引用之中的值,这可以通过在模式中指定 `&` 做到。这让我们得到一个包含引用所指向数据的变量,而不是包含引用的变量。这个技术在通过迭代器遍历引用时,我们需要使用闭包中的值而不是其引用时非常有用。 示例 18-16 中的例子遍历一个 vector 中的 `Point` 实例的引用,并同时解构引用和其中的结构体以方便对 `x` 和 `y` 值进行计算: @@ -321,9 +307,6 @@ let sum_of_squares: i32 = points 示例 18-16: 将结构体的引用解构到其字段值中 - - - 这段代码的结果是变量 `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 }); 这将复杂的类型分解成部分组件以便可以单独使用我们感兴趣的值。 - - - 通过模式解构是一个方便利用部分值片段的手段,比如结构体中每个单独字段的值。 ### 忽略模式中的值 @@ -384,21 +363,12 @@ fn main() { 示例 18-17: 在函数签名中使用 `_` - - - -这段代码会完全忽略作为第一个参数传递的值,3,并会打印出 `This code only uses the y parameter: 4`。大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。 +这段代码会完全忽略作为第一个参数传递的值 `3`,并会打印出 `This code only uses the y parameter: 4`。 -在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样。 +大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样。 #### 使用嵌套的 `_` 忽略部分值 - - - 当只需要测试部分值但在期望运行的代码部分中没有使用它们时,也可以在另一个模式内部使用 `_` 来只忽略部分值。示例 18-18 展示了负责从设置中获取一个值的代码。业务需求是用户不允许覆盖某个设置中已经存在的自定义配置,但是可以重设设置和在目前未设置时提供新的设置。 ```rust @@ -423,14 +393,8 @@ println!("setting is {:?}", setting_value); 对于所有其他情况(`setting_value` 或 `new_setting_value` 任一为 `None`),这由第二个分支的 `_` 模式体现,这时确实希望允许 `new_setting_value` 变为 `setting_value`。 - - - 也可以在一个模式中的多处使用下划线来忽略特定值,如示例 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() { } ``` -示例 18-24: 用 `..` 匹配元组中的第一个和最后一个值并忽略掉所有其它值 +示例 18-24: 只匹配元组中的第一个和最后一个值并忽略掉所有其它值 这里用 `first` 和 `last` 来匹配第一个和最后一个值。`..` 将匹配并忽略中间的所有值。 @@ -538,7 +502,7 @@ fn main() { 文件名: src/main.rs -```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` 中: - - - - -```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); -``` - -示例 18-26: 在匹配分支的模式中创建获取值所有权的变量 - -这个例子会编译失败,因为当 `name` 绑定 `robot_name` 的 `Some` 中的值时,其被移动到了 `match` 中。因为 `robot_name` 的部分所有权被移动到了 `name` 中,就不再能够在 `match` 之后的 `println!` 中使用 `robot_name`,因为 `robot_name` 不再有所有权。 - - - - - - - -为了修复这段代码,需要让 `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); -``` - -示例 18-27: 创建一个引用以便模式变量不会获取其所有权 - -这个例子可以编译,因为 `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); -``` - -示例 18-28: 在模式中使用 `ref mut` 来创建一个值的可变引用 - -上例可以编译并打印出 `robot_name is: Some("Another name")`。因为 `name` 是一个可变引用,我们需要在匹配分支代码中使用 `*` 运算符解引用以便能够修改它。 +Rust 不可能决定在元组中匹配 `second` 值之前应该忽略多少个值,以及在之后忽略多少个值。这段代码可能表明我们意在忽略 `2`,绑定 `second` 为 `4`,接着忽略 `8`、`16` 和 `32`;抑或是意在忽略 `2` 和 `4`,绑定 `second` 为 `8`,接着忽略 `16` 和 `32`,以此类推。变量名 `second` 对于 Rust 来说并没有任何特殊意义,所以会得到编译错误,因为在这两个地方使用 `..` 是有歧义的。 ### 匹配守卫提供的额外条件 - - **匹配守卫**(*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 { } ``` -示例 18-29: 在模式中加入匹配守卫 +示例 18-26: 在模式中加入匹配守卫 -例18-27: 往一个模式中加入匹配守卫 - -上例会打印出 `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` 的条件,所以匹配守卫提供了表现此逻辑的能力。 - - - -在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。实例 18-30 展示了如何使用匹配守卫修复这个问题: - - - +在示例 18-11 中,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。新变量意味着不能够测试外部变量的值。实例 18-27 展示了如何使用匹配守卫修复这个问题: 文件名: src/main.rs @@ -688,27 +571,13 @@ fn main() { } ``` -示例 18-30: 使用匹配守卫来测试与外部变量的相等性 +示例 18-27: 使用匹配守卫来测试与外部变量的相等性 现在这会打印出 `Default case, x = Some(5)`。现在第二个匹配分支中的模式不会引入一个覆盖外部 `y` 的新变量 `y`,这意味着可以在匹配守卫中使用外部的 `y`。相比指定会覆盖外部 `y` 的模式 `Some(y)`,这里指定为 `Some(n)`。此新建的变量 `n` 并没有覆盖任何值,因为 `match` 外部没有变量 `n`。 在匹配守卫 `if n == y` 中,这并不是一个模式所以没有引入新变量。这个 `y` **正是** 外部的 `y` 而不是新的覆盖变量 `y`,这样就可以通过比较 `n` 和 `y` 来表达寻找一个与外部 `y` 相同的值的概念了。 - - - -也可以在匹配守卫中使用或运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用域所有的模式。示例 18-31 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 4、5 **和** 6,即使这看起来好像 `if y` 只作用于 6: - - - +也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,同时匹配守卫的条件会作用域所有的模式。示例 18-28 展示了结合匹配守卫与使用了 `|` 的模式的优先级。这个例子中重要的部分是匹配守卫 `if y` 作用于 `4`、`5` **和** `6`,即使这看起来好像 `if y` 只作用于 `6`: ```rust let x = 4; @@ -720,17 +589,9 @@ match x { } ``` -示例 18-31: 结合多个模式与匹配守卫 - -这个匹配条件表明此分支值匹配 `x` 值为 4、5 或 6 **同时** `y` 为 `true` 的情况。运行这段代码时会发生的是第一个分支的模式因 `x` 为 4 而匹配,不过匹配守卫 `if y` 为假,所以第一个分支不会被选择。代码移动到第二个分支,这会匹配,此程序会打印出 `no`。 +示例 18-28: 结合多个模式与匹配守卫 - - - -这是因为 `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 --> ### `@` 绑定 - - - -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 { } ``` -示例 18-32: 使用 `@` 在模式中绑定值的同时测试它 +示例 18-29: 使用 `@` 在模式中绑定值的同时测试它 上例会打印出 `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`。Rust 会抱怨 `Some(name)` 不匹配 `&Option`,所以不得不这么写: + +```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` 语句和函数参数的模式使得这些结构更强大,可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。