update to ch06-03

pull/584/head
KaiserY 3 years ago
parent 7f827e9fab
commit f1b94ab927

@ -1,5 +1,5 @@
// ANCHOR: here
#[derive(Debug)] // so we can inspect the state in a minute
#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {
Alabama,
Alaska,

@ -1,11 +1,11 @@
// ANCHOR: here
struct QuitMessage; // unit struct
struct QuitMessage; // 类单元结构体
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体
// ANCHOR_END: here
fn main() {}

@ -9,7 +9,7 @@ fn main() {
// ANCHOR: here
impl Message {
fn call(&self) {
// method body would be defined here
// 在这里定义方法体
}
}

@ -2,7 +2,7 @@
> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/main/src/ch06-00-enums.md)
> <br>
> commit a5a03d8f61a5b2c2111b21031a3f526ef60844dd
> commit 40c98a5118d2ba93ce9fd39c313bcbce5597818d
本章介绍 **枚举***enumerations*),也被称作 *enums*。枚举允许你通过列举可能的 **成员***variants* 来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 `Option`,它代表一个值要么是某个值要么什么都不是。然后会讲到在 `match` 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 `if let`,另一个简洁方便处理代码中枚举的结构。

@ -2,19 +2,16 @@
> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/main/src/ch06-01-defining-an-enum.md)
> <br>
> commit a5a03d8f61a5b2c2111b21031a3f526ef60844dd
> commit d3740fb7aad0ea4a80ae20f64dee3a8cfc0c5c3c
让我们看看一个需要诉诸于代码的场景,来考虑为何此时使用枚举更为合适且实用。假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准IPv4version four和 IPv6version six。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 **枚举** 出所有可能的值,这也正是此枚举名字的由来。
枚举是一个不同于结构体的定义自定义数据库类型的方式。让我们看看一个需要诉诸于代码的场景,来考虑为何此时使用枚举更为合适且实用。假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准IPv4version four和 IPv6version six。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 **枚举** 出所有可能的值,这也正是此枚举名字的由来。
任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的而且不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景因为枚举值只可能是其中一个成员。IPv4 和 IPv6 从根本上讲仍是 IP 地址,所以当代码在处理适用于任何类型的 IP 地址的场景时应该把它们当作相同的类型。
可以通过在代码中定义一个 `IpAddrKind` 枚举来表现这个概念并列出可能的 IP 地址类型,`V4` 和 `V6`。这被称为枚举的 **成员***variants*
```rust
enum IpAddrKind {
V4,
V6,
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:def}}
```
现在 `IpAddrKind` 就是一个可以在代码中使用的自定义数据类型了。
@ -24,62 +21,25 @@ enum IpAddrKind {
可以像这样创建 `IpAddrKind` 两个不同成员的实例:
```rust
# enum IpAddrKind {
# V4,
# V6,
# }
#
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:instance}}
```
注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。这么设计的益处是现在 `IpAddrKind::V4``IpAddrKind::V6` 都是 `IpAddrKind` 类型的。例如,接着可以定义一个函数来获取任何 `IpAddrKind`
```rust
# enum IpAddrKind {
# V4,
# V6,
# }
#
fn route(ip_type: IpAddrKind) { }
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:fn}}
```
现在可以使用任一成员来调用这个函数:
```rust
# enum IpAddrKind {
# V4,
# V6,
# }
#
# fn route(ip_type: IpAddrKind) { }
#
route(IpAddrKind::V4);
route(IpAddrKind::V6);
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:fn_call}}
```
使用枚举甚至还有更多优势。进一步考虑一下我们的 IP 地址类型,目前没有一个存储实际 IP 地址 **数据** 的方法;只知道它是什么 **类型** 的。考虑到已经在第五章学习过结构体了,你可能会像示例 6-1 那样处理这个问题:
```rust
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-01/src/main.rs:here}}
```
<span class="caption">示例 6-1将 IP 地址的数据和 `IpAddrKind` 成员存储在一个 `struct`</span>
@ -89,35 +49,19 @@ let loopback = IpAddr {
我们可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。`IpAddr` 枚举的新定义表明了 `V4``V6` 成员都关联了 `String` 值:
```rust
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-02-enum-with-data/src/main.rs:here}}
```
我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。
我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。这里也很容易看出枚举工作的另一个细节:每一个我们定义的枚举成员的名字也变成了一个构建枚举的实例的函数。也就是说,`IpAddr::V4()` 是一个获取 `String` 参数并返回 `IpAddr` 类型实例的函数调用。作为定义枚举的结果,这些构造函数会自动被定义。
用枚举替代结构体还有另一个优势每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 `V4` 地址存储为四个 `u8` 值而 `V6` 地址仍然表现为一个 `String`,这就不能使用结构体了。枚举则可以轻易的处理这个情况:
```rust
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-03-variants-with-different-data/src/main.rs:here}}
```
这些代码展示了使用枚举来存储两种不同 IP 地址的几种可能的选择。然而,事实证明存储和编码 IP 地址实在是太常见了[以致标准库提供了一个开箱即用的定义!][IpAddr]<!-- ignore -->让我们看看标准库是如何定义 `IpAddr` 的:它正有着跟我们定义和使用的一样的枚举和成员,不过它将成员中的地址数据嵌入到了两个不同形式的结构体中,它们对不同的成员的定义是不同的:
[IpAddr]: https://doc.rust-lang.org/std/net/enum.IpAddr.html
```rust
struct Ipv4Addr {
// --snip--
@ -140,12 +84,7 @@ enum IpAddr {
来看看示例 6-2 中的另一个枚举的例子:它的成员中内嵌了多种多样的类型:
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-02/src/main.rs:here}}
```
<span class="caption">示例 6-2一个 `Message` 枚举,其每个成员都存储了不同数量和类型的值</span>
@ -153,20 +92,14 @@ enum Message {
这个枚举有四个含有不同类型的成员:
* `Quit` 没有关联任何数据。
* `Move` 包含一个匿名结构体
* `Move` 类似结构体包含命名字段
* `Write` 包含单独一个 `String`
* `ChangeColor` 包含三个 `i32`
定义一个如示例 6-2 中所示那样的有关联值的枚举的方式和定义多个不同类型的结构体的方式很相像,除了枚举不使用 `struct` 关键字以及其所有成员都被组合在一起位于 `Message` 类型下。如下这些结构体可以包含与之前枚举成员中相同的数据:
```rust
struct QuitMessage; // 类单元结构体
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-04-structs-similar-to-message-enum/src/main.rs:here}}
```
不过,如果我们使用不同的结构体,由于它们都有不同的类型,我们将不能像使用示例 6-2 中定义的 `Message` 枚举那样,轻易的定义一个能够处理这些不同类型的结构体的函数,因为枚举是单独一个类型。
@ -174,21 +107,7 @@ struct ChangeColorMessage(i32, i32, i32); // 元组结构体
结构体和枚举还有另一个相似点:就像可以使用 `impl` 来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们 `Message` 枚举上的叫做 `call` 的方法:
```rust
# enum Message {
# Quit,
# Move { x: i32, y: i32 },
# Write(String),
# ChangeColor(i32, i32, i32),
# }
#
impl Message {
fn call(&self) {
// 在这里定义方法体
}
}
let m = Message::Write(String::from("hello"));
m.call();
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-05-methods-on-enums/src/main.rs:here}}
```
方法体使用了 `self` 来获取调用方法的值。这个例子中,创建了一个值为 `Message::Write(String::from("hello"))` 的变量 `m`,而且这就是当 `m.call()` 运行时 `call` 方法中的 `self` 的值。
@ -197,7 +116,7 @@ m.call();
### `Option` 枚举和其相对于空值的优势
在之前的部分,我们看到了 `IpAddr` 枚举如何利用 Rust 的类型系统在程序中编码更多信息而不单单是数据。接下来我们分析一个 `Option` 的案例,`Option` 是标准库定义的另一个枚举。`Option` 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
这一部分会分析一个 `Option` 的案例,`Option` 是标准库定义的另一个枚举。`Option` 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。例如,如果请求一个包含项的列表的第一个值,会得到一个值,如果请求一个空的列表,就什么也不会得到。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
编程语言的设计经常要考虑包含哪些功能但考虑排除哪些功能也很重要。Rust 并没有很多其他语言中有的空值功能。**空值***Null* )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
@ -220,59 +139,47 @@ Tony Hoarenull 的发明者,在他 2009 年的演讲 “Null References: Th
问题不在于概念而在于具体的实现。为此Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 `Option<T>`,而且它[定义于标准库中][option]<!-- ignore -->,如下:
[option]: https://doc.rust-lang.org/std/option/enum.Option.html
```rust
enum Option<T> {
Some(T),
None,
Some(T),
}
```
`Option<T>` 枚举是如此有用以至于它甚至被包含在了 prelude 之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 `Option::` 前缀来直接使用 `Some``None`。即便如此 `Option<T>` 也仍是常规的枚举,`Some(T)` 和 `None` 仍是 `Option<T>` 的成员。
`<T>` 语法是一个我们还未讲到的 Rust 功能。它是一个泛型类型参数,第十章会更详细的讲解泛型。目前,所有你需要知道的就是 `<T>` 意味着 `Option` 枚举的 `Some` 成员可以包含任意类型的数据。这里是一些包含数字类型和字符串类型 `Option` 值的例子:
`<T>` 语法是一个我们还未讲到的 Rust 功能。它是一个泛型类型参数,第十章会更详细的讲解泛型。目前,所有你需要知道的就是 `<T>` 意味着 `Option` 枚举的 `Some` 成员可以包含任意类型的数据,同时每一个用于 `T` 位置的具体类型使得 `Option<T>` 整体作为不同的类型。这里是一些包含数字类型和字符串类型 `Option` 值的例子:
```rust
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-06-option-examples/src/main.rs:here}}
```
如果使用 `None` 而不是 `Some`,需要告诉 Rust `Option<T>` 是什么类型的,因为编译器只通过 `None` 值无法推断出 `Some` 成员保存的值的类型。
`some_number` 的类型是 `Option<i32>`。`some_string` 的类型是 `Option<&str>`,这(与 `some_number`)是一个不同的类型。因为我们在 `Some` 成员中指定了值Rust 可以推断其类型。对于 `absent_number` Rust 需要我们指定 `Option` 整体的类型,因为编译器只通过 `None` 值无法推断出 `Some` 成员保存的值的类型。这里我们告诉 Rust 希望 `absent_number``Option<i32>` 类型的。
当有一个 `Some` 值时,我们就知道存在一个值,而这个值保存在 `Some` 中。当有个 `None` 值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值。那么,`Option<T>` 为什么就比空值要好呢?
简而言之,因为 `Option<T>``T`(这里 `T` 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 `Option<T>`。例如,这段代码不能编译,因为它尝试将 `Option<i8>``i8` 相加:
```rust,ignore
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-07-cant-use-option-directly/src/main.rs:here}}
```
如果运行这些代码,将得到类似这样的错误信息:
```text
error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is
not satisfied
-->
|
5 | let sum = x + y;
| ^ no implementation for `i8 + std::option::Option<i8>`
|
```console
{{#include ../listings/ch06-enums-and-pattern-matching/no-listing-07-cant-use-option-directly/output.txt}}
```
很好!事实上,错误信息意味着 Rust 不知道该如何将 `Option<i8>``i8` 相加,因为它们的类型不同。当在 Rust 中拥有一个像 `i8` 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需做空值检查。只有当使用 `Option<i8>`(或者任何用到的类型)的时候需要担心可能没有值,而编译器会确保我们在使用值之前处理了为空的情况。
换句话说,在对 `Option<T>` 进行 `T` 的运算之前必须将其转换为 `T`。通常这能帮助我们捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况。
不再担心会错误的假设一个非空值,会让你对代码更加有信心。为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的 `Option<T>` 中。接着,当使用这个值时,必须明确的处理值为空的情况。只要一个值不是 `Option<T>` 类型,你就 **可以** 安全的认定它的值不为空。这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。
消除了错误地假设一个非空值的风险,会让你对代码更加有信心。为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的 `Option<T>` 中。接着,当使用这个值时,必须明确的处理值为空的情况。只要一个值不是 `Option<T>` 类型,你就 **可以** 安全的认定它的值不为空。这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。
那么当有一个 `Option<T>` 的值时,如何从 `Some` 成员中取出 `T` 的值来使用它呢?`Option<T>` 枚举拥有大量用于各种情况的方法:你可以查看[它的文档][docs]<!-- ignore -->。熟悉 `Option<T>` 的方法将对你的 Rust 之旅非常有用。
[docs]: https://doc.rust-lang.org/std/option/enum.Option.html
总的来说,为了使用 `Option<T>` 值,需要编写处理每个成员的代码。你想要一些代码只当拥有 `Some(T)` 值时运行,允许这些代码使用其中的 `T`。也希望一些代码在值为 `None` 时运行,这些代码并没有一个可用的 `T` 值。`match` 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。
[IpAddr]: https://doc.rust-lang.org/std/net/enum.IpAddr.html
[option]: https://doc.rust-lang.org/std/option/enum.Option.html
[docs]: https://doc.rust-lang.org/std/option/enum.Option.html

@ -2,7 +2,7 @@
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/main/src/ch06-02-match.md)
> <br>
> commit 16a17f936229c4429530b97522ce4ce373f1054e
> commit db403a8bdfe5223d952737f54b0d9651b3e6ae1d
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及它们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
@ -11,21 +11,7 @@ Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我
因为刚刚提到了硬币,让我们用它们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示。
```rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-03/src/main.rs:here}}
```
<span class="caption">示例 6-3一个枚举和一个以枚举成员作为模式的 `match` 表达式</span>
@ -41,24 +27,7 @@ fn value_in_cents(coin: Coin) -> u8 {
如果分支代码较短的话通常不使用大括号,正如示例 6-3 中的每个分支都只是返回一个值。如果想要在分支中运行多行代码,可以使用大括号。例如,如下代码在每次使用`Coin::Penny` 调用时都会打印出 “Lucky penny!”,同时仍然返回代码块最后的值,`1`
```rust
# enum Coin {
# Penny,
# Nickel,
# Dime,
# Quarter,
# }
#
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-08-match-arm-multiple-lines/src/main.rs:here}}
```
### 绑定值的模式
@ -68,19 +37,7 @@ fn value_in_cents(coin: Coin) -> u8 {
作为一个例子让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美国在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的 `enum`,通过改变 `Quarter` 成员来包含一个 `State` 值,示例 6-4 中完成了这些修改:
```rust
#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-04/src/main.rs:here}}
```
<span class="caption">示例 6-4`Quarter` 成员也存放了一个 `UsState` 值的 `Coin` 枚举</span>
@ -90,30 +47,7 @@ enum Coin {
在这些代码的匹配表达式中,我们在匹配 `Coin::Quarter` 成员的分支的模式中增加了一个叫做 `state` 的变量。当匹配到 `Coin::Quarter` 时,变量 `state` 将会绑定 25 美分硬币所对应州的值。接着在那个分支的代码中使用 `state`,如下:
```rust
# #[derive(Debug)]
# enum UsState {
# Alabama,
# Alaska,
# }
#
# enum Coin {
# Penny,
# Nickel,
# Dime,
# Quarter(UsState),
# }
#
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-09-variable-in-pattern/src/main.rs:here}}
```
如果调用 `value_in_cents(Coin::Quarter(UsState::Alaska))``coin` 将是 `Coin::Quarter(UsState::Alaska)`。当将值与每个分支相比较时,没有分支会匹配,直到遇到 `Coin::Quarter(state)`。这时,`state` 绑定的将会是值 `UsState::Alaska`。接着就可以在 `println!` 表达式中使用这个绑定了,像这样就可以获取 `Coin` 枚举的 `Quarter` 成员中内部的州的值。
@ -127,16 +61,7 @@ fn value_in_cents(coin: Coin) -> u8 {
得益于 `match`,编写这个函数非常简单,它将看起来像示例 6-5 中这样:
```rust
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:here}}
```
<span class="caption">示例 6-5一个在 `Option<i32>` 上使用 `match` 表达式的函数</span>
@ -146,13 +71,13 @@ let none = plus_one(None);
让我们更仔细地检查 `plus_one` 的第一行操作。当调用 `plus_one(five)` 时,`plus_one` 函数体中的 `x` 将会是值 `Some(5)`。接着将其与每个分支比较。
```rust,ignore
None => None,
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:first_arm}}
```
`Some(5)` 并不匹配模式 `None`,所以继续进行下一个分支。
```rust,ignore
Some(i) => Some(i + 1),
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:second_arm}}
```
`Some(5)``Some(i)` 匹配吗?当然匹配!它们是相同的成员。`i` 绑定了 `Some` 中包含的值,所以 `i` 的值是 `5`。接着匹配分支的代码被执行,所以我们将 `i` 的值加一并返回一个含有值 `6` 的新 `Some`
@ -160,7 +85,7 @@ Some(i) => Some(i + 1),
接着考虑下示例 6-5 中 `plus_one` 的第二个调用,这里 `x``None`。我们进入 `match` 并与第一个分支相比较。
```rust,ignore
None => None,
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:first_arm}}
```
匹配上了!这里没有值来加一,所以程序结束并返回 `=>` 右侧的值 `None`,因为第一个分支就匹配到了,其他的分支将不再比较。
@ -172,21 +97,13 @@ None => None,
`match` 还有另一方面需要讨论。考虑一下 `plus_one` 函数的这个版本,它有一个 bug 并不能编译:
```rust,ignore,does_not_compile
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/src/main.rs:here}}
```
我们没有处理 `None` 的情况,所以这些代码会造成一个 bug。幸运的是这是一个 Rust 知道如何处理的 bug。如果尝试编译这段代码会得到这个错误
```text
error[E0004]: non-exhaustive patterns: `None` not covered
-->
|
6 | match x {
| ^ pattern `None` not covered
```console
{{#include ../listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/output.txt}}
```
Rust 知道我们没有覆盖所有可能的情况甚至知道哪些模式被忘记了Rust 中的匹配是 **穷尽的***exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这让我们免于假设拥有一个实际上为空的值,从而使之前提到的价值亿万的错误不可能发生。
@ -196,16 +113,7 @@ Rust 知道我们没有覆盖所有可能的情况甚至知道哪些模式被忘
让我们看一个例子,我们希望对一些特定的值采取特殊操作,而对其他的值采取默认操作。想象我们正在玩一个游戏,如果你掷出骰子的值为 3角色不会移动而是会得到一顶新奇的帽子。如果你掷出了 7你的角色将失去新奇的帽子。对于其他的数值你的角色会在棋盘上移动相应的格子。这是一个实现了上述逻辑的 `match`,骰子的结果是硬编码而不是一个随机值,其他的逻辑部分使用了没有函数体的函数来表示,实现它们超出了本例的范围:
```rust
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-15-binding-catchall/src/main.rs:here}}
```
对于前两个分支,匹配模式是字面值 3 和 7最后一个分支则涵盖了所有其他可能的值模式是我们命名为 `other` 的一个变量。`other` 分支的代码通过将其传递给 `move_player` 函数来使用这个变量。
@ -217,38 +125,20 @@ Rust 还提供了一个模式,当我们不想使用通配模式获取的值时
让我们改变游戏规则,当你掷出的值不是 3 或 7 的时候,你必须再次掷出。这种情况下我们不需要使用这个值,所以我们改动代码使用 `_` 来替代变量 `other`
```rust
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-16-underscore-catchall/src/main.rs:here}}
```
这个例子也满足穷举性要求,因为我们在最后一个分支中明确地忽略了其他的值。我们没有忘记处理任何东西。
让我们再次改变游戏规则,如果你掷出 3 或 7 以外的值,你的回合将无事发生。我们可以使用单元值(在[“元组类型”][tuples]<!-- ignore -->一节中提到的空元组)作为 `_` 分支的代码:
[tuples]: ch03-02-data-types.html#元组类型
```rust
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-17-underscore-unit/src/main.rs:here}}
```
在这里,我们明确告诉 Rust 我们不会使用与前面模式不匹配的值,并且这种情况下我们不想运行任何代码。
我们将在[第 18 章][ch18-00-patterns]<!-- ignore -->中介绍更多关于模式和匹配的内容。现在,让我们继续讨论 `if let` 语法,这在 `match` 表达式有点啰嗦的情况下很有用。
[tuples]: ch03-02-data-types.html#元组类型
[ch18-00-patterns]: ch18-00-patterns.html

@ -2,32 +2,25 @@
> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/main/src/ch06-03-if-let.md)
> <br>
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit 40c98a5118d2ba93ce9fd39c313bcbce5597818d
`if let` 语法让我们以一种不那么冗长的方式结合 `if``let`,来处理只匹配一个模式的值而忽略其他模式的情况。考虑示例 6-6 中的程序,它匹配一个 `Option<u8>` 值并只希望当值为 3 时执行代码:
`if let` 语法让我们以一种不那么冗长的方式结合 `if``let`,来处理只匹配一个模式的值而忽略其他模式的情况。考虑示例 6-6 中的程序,它匹配一个 `config_max` 变量中的 `Option<u8>` 值并只希望当值为 `Some` 成员时执行代码:
```rust
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-06/src/main.rs:here}}
```
<span class="caption">示例 6-6`match` 只关心当值为 `Some(3)` 时执行代码</span>
<span class="caption">示例 6-6`match` 只关心当值为 `Some` 时执行代码</span>
我们想要对 `Some(3)` 匹配进行操作但是不想处理任何其他 `Some<u8>` 值或 `None`。为了满足 `match` 表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上 `_ => ()`,这样也要增加很多样板代码。
如果值是 `Some`,我们希望打印出 `Some` 成员中的值,这个值被绑定到模式中的 `max` 变量里。对于 `None` 值我们不希望做任何操作。为了满足 `match` 表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上 `_ => ()`,这样也要增加很多烦人的样板代码。
不过我们可以使用 `if let` 这种更短的方式编写。如下代码与示例 6-6 中的 `match` 行为一致:
```rust
# let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-12-if-let/src/main.rs:here}}
```
`if let` 获取通过等号分隔的一个模式和一个表达式。它的工作方式与 `match` 相同,这里的表达式对应 `match` 而模式则对应第一个分支。
`if let` 语法获取通过等号分隔的一个模式和一个表达式。它的工作方式与 `match` 相同,这里的表达式对应 `match` 而模式则对应第一个分支。在这个例子中,模式是 `Some(max)``max` 绑定为 `Some` 中的值。接着可以在 `if let` 代码块中使用 `max` 了,就跟在对应的 `match` 分支中一样。模式不匹配时 `if let` 块中的代码不会执行。
使用 `if let` 意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 `match` 强制要求的穷尽性检查。`match` 和 `if let` 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
@ -36,48 +29,13 @@ if let Some(3) = some_u8_value {
可以在 `if let` 中包含一个 `else`。`else` 块中的代码与 `match` 表达式中的 `_` 分支块中的代码相同,这样的 `match` 表达式就等同于 `if let``else`。回忆一下示例 6-4 中 `Coin` 枚举的定义,其 `Quarter` 成员也包含一个 `UsState` 值。如果想要计数所有不是 25 美分的硬币的同时也报告 25 美分硬币所属的州,可以使用这样一个 `match` 表达式:
```rust
# #[derive(Debug)]
# enum UsState {
# Alabama,
# Alaska,
# }
#
# enum Coin {
# Penny,
# Nickel,
# Dime,
# Quarter(UsState),
# }
# let coin = Coin::Penny;
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-13-count-and-announce-match/src/main.rs:here}}
```
或者可以使用这样的 `if let``else` 表达式:
```rust
# #[derive(Debug)]
# enum UsState {
# Alabama,
# Alaska,
# }
#
# enum Coin {
# Penny,
# Nickel,
# Dime,
# Quarter(UsState),
# }
# let coin = Coin::Penny;
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-14-count-and-announce-if-let-else/src/main.rs:here}}
```
如果你的程序遇到一个使用 `match` 表达起来过于啰嗦的逻辑,记住 `if let` 也在你的 Rust 工具箱中。

Loading…
Cancel
Save