check ch06-03

This commit is contained in:
KaiserY 2017-08-16 17:49:20 +08:00
parent cf22db5871
commit 2c5097b783
9 changed files with 209 additions and 219 deletions

View File

@ -20,13 +20,15 @@
- [引用 & 借用](ch04-02-references-and-borrowing.md)
- [Slices](ch04-03-slices.md)
- [结构体](ch05-00-structs.md)
- [方法语法](ch05-01-method-syntax.md)
- [使用结构体组织相关联的数据](ch05-00-structs.md)
- [定义并实例化结构体](ch05-01-defining-structs.md)
- [一个使用结构体的示例程序](ch05-02-example-structs.md)
- [方法语法](ch05-03-method-syntax.md)
- [枚举和模式匹配](ch06-00-enums.md)
- [定义枚举](ch06-01-defining-an-enum.md)
- [`match`控制流运算符](ch06-02-match.md)
- [`if let`简单控制流](ch06-03-if-let.md)
- [`match` 控制流运算符](ch06-02-match.md)
- [`if let` 简单控制流](ch06-03-if-let.md)
## 基本 Rust 技能

View File

@ -1,4 +1,4 @@
# 结构体
# 使用结构体组织相关联的数据
> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-00-structs.md)
> <br>

View File

@ -4,9 +4,9 @@
> <br>
> commit 56352c28cf3fe0402fa5a7cba73890e314d720eb
对结构体的一种看法是他们与元组类似,这个我们在第三章讲过了。就像元组,结构体的每一部分可以是不同类型。可以命名各部分数据以便能更清楚的知道其值的意义。由于有了这些名字使得结构体更灵活:不需要依赖顺序来指定或访问实例中的值。
我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
为了定义结构体,通过`struct`关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,他们被称作**字段***field*),并定义字段类型。例如,列表 5-1 展示了一个储存用户账号信息的结构体:
为了定义结构体,通过 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,他们被称作 **字段***field*),并定义字段类型。例如,列表 5-1 展示了一个储存用户账号信息的结构体:
```rust
struct User {
@ -17,9 +17,9 @@ struct User {
}
```
<span class="caption">Listing 5-1: A `User` struct definition</span>
<span class="caption">列表 5-1`User` 结构体定义</span>
一旦定义了结构体后为了使用它,通过为每个字段指定具体值来创建这个结构体的**实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用`key: value`对的形式提供字段,其中 key 是字段的名字而 value 是需要储存在字段中的数据值。这时字段的顺序并不必要与在结构体中声明他们的顺序一致。换句话说,结构体的定义就像一个这个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,我们可以像这样来声明一个特定的用户:
一旦定义了结构体后为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 对的形式提供字段,其中 key 是字段的名字而 value 是需要储存在字段中的数据值。这时字段的顺序并不必要与在结构体中声明他们的顺序一致。换句话说,结构体的定义就像一个这个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像列表 5-2 这样来声明一个特定的用户:
```rust
# struct User {
@ -37,7 +37,9 @@ let user1 = User {
};
```
为了从结构体中获取某个值,可以使用点号。如果我们只想要用户的邮箱地址,可以用`user1.email`。要更改结构体中的值,如果结构体的实例是可变的,我们可以使用点号并对对应的字段赋值。列表 5-3 展示了如何改变一个可变的`User`实例`email`字段的值:
<span class="caption">列表 5-2创建 `User` 结构体的实例</span>
为了从结构体中获取某个值,可以使用点号。如果我们只想要用户的邮箱地址,可以用 `user1.email`。要更改结构体中的值,如果结构体的实例是可变的,我们可以使用点号并对对应的字段赋值。列表 5-3 展示了如何改变一个可变的 `User` 实例 `email` 字段的值:
```rust
# struct User {
@ -57,10 +59,9 @@ let mut user1 = User {
user1.email = String::from("anotheremail@example.com");
```
<span class="caption">Listing 5-3: Changing the value in the `email` field of a
`User` instance</span>
<span class="caption">列表 5-3改变 `User` 结构体 `email` 字段的值</span>
与其他任何表达式一样,我们可以在函数体的最后一个表达式构造一个结构体,从函数隐式的返回一个结构体的新实例。表 5-4 显示了一个返回带有给定的`email`与`username`的`User`结构体的实例的`build_user`函数。`active`字段的值为`true`,并且`sign_in_count`的值为`1`。
与其他任何表达式一样,我们可以在函数体的最后一个表达式构造一个结构体,从函数隐式的返回一个结构体的新实例。表 5-4 显示了一个返回带有给定的 `email` `username` `User` 结构体的实例的 `build_user` 函数。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1`
```rust
# struct User {
@ -80,16 +81,15 @@ fn build_user(email: String, username: String) -> User {
}
```
<span class="caption">Listing 5-4: A `build_user` function that takes an email
and username and returns a `User` instance</span>
<span class="caption">列表 5-4`build_user` 函数获取 email 和用户名并返回 `User` 实例</span>
不过,重复`email`字段与`email`变量,同样的对于`username`,感觉有一点无趣。将函数参数起与结构体字段相同的名字是可以理解的,但是如果结构体有更多字段,重复他们是十分烦人的。幸运的是,我们有一个方便的语法!
不过,重复 `email` 字段与 `email` 变量的名字,同样的对于`username`,感觉有一点无趣。将函数参数起与结构体字段相同的名字是可以理解的,但是如果结构体有更多字段,重复他们是十分烦人的。幸运的是,这里有一个方便的语法!
### 变量与字段同名时的字段初始化语法
如果有变量与字段同名的话,你可以使用**字段初始化语法**。这可以让创建新的结构体实例的函数更为简练。
如果有变量与字段同名的话,你可以使用 **字段初始化语法***field init shorthand*。这可以让创建新的结构体实例的函数更为简练。
在列表 5-4 中,名为`email`与`username`的参数与结构体`User`的字段`email`和`username`同名。因为名字相同,我们可以写出不重复`email`和`username`的`build_user`函数,如列表 5-5 所示。 这个版本的函数与列表 5-4 中代码的行为完全相同。这个字段初始化语法可以让这类代码更简洁,特别是当结构体有很多字段的时候。
在列表 5-4 中,名为 `email` `username` 的参数与结构体 `User` 的字段 `email` `username` 同名。因为名字相同,我们可以写出不重复 `email` `username` `build_user` 函数,如列表 5-5 所示。 这个版本的函数与列表 5-4 中代码的行为完全相同。这个字段初始化语法可以让这类代码更简洁,特别是当结构体有很多字段的时候。
```rust
# struct User {
@ -109,13 +109,11 @@ fn build_user(email: String, username: String) -> User {
}
```
<span class="caption">Listing 5-5: A `build_user` function that uses field init
syntax since the `email` and `username` parameters have the same name as struct
fields</span>
<span class="caption">列表 5-5`build_user` 函数使用了字段初始化语法,因为 `email``username` 参数与结构体字段同名</span>
### 使用结构体更新语法从其他对象创建对象
可以从老的对象创建新的对象常常是很有帮助的,即复用大部分老对象的值但是改变一部分。列表 5-6 展示了一个设置`email`与`username`的值但其余字段使用与列表 5-2 中`user1`实例相同的值以创建新的`User`实例`user2`的例子。
可以从老的对象创建新的对象常常是很有帮助的,即复用大部分老对象的值并只改变一部分。列表 5-6 展示了一个设置 `email``username` 的值但其余字段使用与列表 5-2 中 `user1` 实例相同的值以创建新的 `User` 实例 `user2` 的例子:
```rust
# struct User {
@ -140,10 +138,9 @@ let user2 = User {
};
```
<span class="caption">Listing 5-6: Creating a new `User` instance, `user2`, and
setting some fields to the values of the same fields from `user1`</span>
<span class="caption">列表 5-6创建 `User` 新实例,`user2`,并将一些字段的值设置为 `user1` 同名字段的值</span>
**结构体更新语法**可以利用更少的代码获得与列表 5-6 相同的效果。结构体更新语法利用`..`以指定未显式设置的字段应有与给定实例对应字段相同的值。列表 5-7 中的代码同样地创建了有着不同的`email`与`username`的值但`active`和`sign_in_count`字段与`user1`相同的实例`user2`
**结构体更新语法***struct update syntax*可以利用更少的代码获得与列表 5-6 相同的效果。结构体更新语法利用 `..` 以指定未显式设置的字段应有与给定实例对应字段相同的值。列表 5-7 中的代码同样地创建了有着不同的 `email``username` 值但 `active``sign_in_count` 字段与 `user1` 相同的实例 `user2`
```rust
# struct User {
@ -167,13 +164,11 @@ let user2 = User {
};
```
<span class="caption">Listing 5-7: Using struct update syntax to set a new
`email` and `username` values for a `User` instance but use the rest of the
values from the fields of the instance in the `user1` variable</span>
<span class="caption">列表 5-7使用结构体更新语法为 `User` 实例设置新的 `email``username` 值,但使用 `user1` 变量中剩下字段的值</span>
### 使用没有命名字段的元组结构体创建不同的类型
我们也可以定义与元组相像的结构体,称为**元组结构体**,有着结构体名称提供的含义,但没有具体的字段名只有字段的类型。元组结构体的定义仍然以`struct`关键字与结构体名称,接下来是元组的类型。如以下是命名为`Color`与`Point`的元组结构体的定义与使用:
也可以定义与元组相像的结构体,称为 **元组结构体***tuple structs*,有着结构体名称提供的含义,但没有具体的字段名只有字段的类型。元组结构体的定义仍然以`struct` 关键字与结构体名称,接下来是元组的类型。如以下是命名为 `Color` 与`Point` 的元组结构体的定义与使用:
```rust
struct Color(i32, i32, i32);
@ -183,19 +178,19 @@ let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
```
注意`black`和`origin`变量有不同的类型,因为他们是不同的元组结构体的实例。我们定义的每一个结构体有着自己的类型,即使结构体中的字段有相同的类型。在其他方面,元组结构体类似我们在第三章提到的元组。
注意 `black` `origin` 变量有不同的类型,因为他们是不同的元组结构体的实例。我们定义的每一个结构体有着自己的类型,即使结构体中的字段有相同的类型。在其他方面,元组结构体类似我们在第三章提到的元组。
### 没有任何字段的类单元结构体
我们也可以定义一个没有任何字段的结构体!他们被称为**类单元结构体**因为他们类似于`()`,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型内存储数据的时候发挥作用。我们将在第十章介绍 trait。
我们也可以定义一个没有任何字段的结构体!他们被称为 **类单元结构体***unit-like structs*因为他们类似于 `()`,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型内存储数据的时候发挥作用。我们将在第十章介绍 trait。
> ## 结构体数据的所有权
>
> 在列表 5-1 中的`User`结构体的定义中,我们使用了自身拥有所有权的`String`> 类型而不是`&str`字符串 slice 类型。这是一个有意而为之的选择,因为我们想要> 这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也应该是有效> 的。
> 在列表 5-1 中的 `User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是 `&str` 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也应该是有效的。
>
> 可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上**生命周> 期***lifetimes*),这是第十章会讨论的一个 Rust 功能。生命周期确保结> 构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用> 而不指定生命周期,比如这样:
> 可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期***lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用而不指定生命周期,比如这样:
>
> <span class="filename">Filename: src/main.rs</span>
> <span class="filename">文件名: src/main.rs</span>
>
> ```rust,ignore
> struct User {
@ -215,7 +210,7 @@ let origin = Point(0, 0, 0);
> }
> ```
>
> 编译器会抱怨它需要生命周期说明符:
> 编译器会抱怨它需要生命周期标识符:
>
> ```text
> error[E0106]: missing lifetime specifier
@ -230,5 +225,5 @@ let origin = Point(0, 0, 0);
> 3 | email: &str,
> | ^ expected lifetime parameter
> ```
>
> 第十章会讲到如何修复这个问题以便在结构体中储存引用,不过现在,通过从像`&str`这样的引用切换到像`String`这类拥有所有权的类型来修改修改这个错误。
>
> 第十章会讲到如何修复这个问题以便在结构体中储存引用,不过现在,通过从像 `&str` 这样的引用切换到像 `String` 这类拥有所有权的类型来修改修改这个错误。

View File

@ -1,4 +1,4 @@
## 一个示例程序
## 一个使用结构体的示例程序
> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-02-example-structs.md)
> <br>
@ -8,7 +8,7 @@
使用 Cargo 来创建一个叫做 *rectangles* 的新二进制程序,它会获取一个长方形以像素为单位的长度和宽度并计算它的面积。列表 5-2 中是项目的 *src/main.rs* 文件中为此实现的一个小程序:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
@ -26,10 +26,9 @@ fn area(length: u32, width: u32) -> u32 {
}
```
<span class="caption">Listing 5-2: Calculating the area of a rectangle
specified by its length and width in separate variables</span>
<span class="caption">列表 5-8通过指定长方形的长宽变量来计算长方形面积</span>
尝试使用`cargo run`运行程序:
尝试使用 `cargo run` 运行程序:
```text
The area of the rectangle is 1500 square pixels.
@ -37,19 +36,17 @@ The area of the rectangle is 1500 square pixels.
### 使用元组重构
我们的小程序能正常运行;它调用`area`函数用长方形的每个维度来计算出面积。不过我们可以做的更好。长度和宽度是相关联的,因为他们在一起才能定义一个长方形。
虽然列表 5-8 可以运行,并调用 `area` 函数用长方形的每个维度来计算出面积,不过我们可以做的更好。长度和宽度是相关联的,因为他们在一起才能定义一个长方形。
这个做法的问题突显在`area`的签名上:
这个做法的问题突显在 `area` 的签名上:
```rust,ignore
fn area(length: u32, width: u32) -> u32 {
```
函数`area`本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序自身却哪里也没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。
函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序自身却哪里也没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 “将值组合进元组” 部分已经讨论过了一种可行的方法:元组。列表 5-9 是另一个使用元组的版本:
第三章已经讨论过了一种可行的方法:元组。列表 5-3 是一个使用元组的版本:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
@ -66,27 +63,17 @@ fn area(dimensions: (u32, u32)) -> u32 {
}
```
<span class="caption">Listing 5-3: Specifying the length and width of the
rectangle with a tuple</span>
<span class="caption">列表 5-8使用元组来指定长方形的长宽</span>
<!-- I will add ghosting & wingdings once we're in libreoffice /Carol -->
在某种程度上说这样好一点了。元组帮助我们增加了一些结构性,现在在调用 `area` 的时候只用传递一个参数。不过在另一方面这个方法却更不明确了:元组并没有给出它元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分:
在某种程度上说这样好一点了。元组帮助我们增加了一些结构性,现在在调用`area`的时候只用传递一个参数。不过另一方面这个方法却更不明确了:元组并没有给出它元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分:
<!-- I will change this to use wingdings instead of repeating this code once
we're in libreoffice /Carol -->
```rust,ignore
dimensions.0 * dimensions.1
```
在面积计算时混淆长宽并没有什么问题,不过当在屏幕上绘制长方形时就有问题了!我们将不得不记住元组索引`0`是`length`而`1`是`width`。如果其他人要使用这些代码,他们也不得不搞清楚后再记住他们。容易忘记或者混淆这些值而造成错误,因为我们没有表明代码中数据的意义。
在面积计算时混淆长宽并没有什么问题,不过当在屏幕上绘制长方形时就有问题了!我们将不得不记住元组索引 `0``length``1``width`。如果其他人要使用这些代码,他们也不得不搞清楚并记住他们。容易忘记或者混淆这些值而造成错误,因为我们没有表明代码中数据的意义。
### 使用结构体重构:增加更多意义
现在引入结构体的时候了。我们可以将元组转换为一个有整体名称而且每个部分也有对应名字的数据类型,如列表 5-4 所示:
现在引入结构体的时候了。我们可以将元组转换为一个有整体名称而且每个部分也有对应名字的数据类型,如列表 5-10 所示:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
struct Rectangle {
@ -108,21 +95,19 @@ fn area(rectangle: &Rectangle) -> u32 {
}
```
<span class="caption">Listing 5-4: Defining a `Rectangle` struct</span>
<span class="caption">列表 5-10定义 `Rectangle` 结构体</span>
<!-- Will add ghosting & wingdings once we're in libreoffice /Carol -->
这里我们定义了一个结构体并称其为 `Rectangle`。在 `{}` 中定义了字段 `length``width`,都是 `u32` 类型的。接着在 `main` 中,我们创建了一个长度为 50 和宽度为 30 的 `Rectangle` 的具体实例。
这里我们定义了一个结构体并称其为`Rectangle`。在`{}`中定义了字段`length`和`width`,都是`u32`类型的。接着在`main`中,我们创建了一个长度为 50 和宽度为 30 的`Rectangle`的具体实例
函数 `area` 现在被定义为接收一个名叫 `rectangle` 的参数,它的类型是一个结构体 `Rectangle` 实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权这样 `main` 函数就可以保持 `rect1` 的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有 `&`
函数`area`现在被定义为接收一个名叫`rectangle`的参数,它的类型是一个结构体`Rectangle`实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权这样`main`函数就可以保持`rect1`的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有`&`。
`area`函数访问`Rectangle`的`length`和`width`字段。`area`的签名现在明确的表明了我们的意图:通过其`length`和`width`字段,计算一个`Rectangle`的面积。这表明了长度和宽度是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值`0`和`1`。结构体胜在更清晰明了。
`area` 函数访问 `Rectangle``length``width` 字段。`area` 的签名现在明确的表明了我们的意图:通过其 `length``width` 字段,计算一个 `Rectangle` 的面积。这表明了长度和宽度是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值 `0``1` 。结构体胜在更清晰明了。
### 通过衍生 trait 增加实用功能
如果能够在调试程序时打印出`Rectangle`实例来查看其所有字段的值就更好了。列表 5-5 尝试像往常一样使用`println!`宏:
如果能够在调试程序时打印出 `Rectangle` 实例来查看其所有字段的值就更好了。列表 5-11 像往常一样使用 `println!` 宏:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
struct Rectangle {
@ -137,8 +122,7 @@ fn main() {
}
```
<span class="caption">Listing 5-5: Attempting to print a `Rectangle`
instance</span>
<span class="caption">列表 5-11尝试打印出 `Rectangle` 实例</span>
如果运行代码,会出现带有如下核心信息的错误:
@ -146,7 +130,7 @@ instance</span>
error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
```
`println!`宏能处理很多类型的格式,不过,`{}`,默认告诉`println!`使用称为`Display`的格式:直接提供给终端用户查看的输出。目前为止见过的基本类型都默认实现了`Display`,所以它就是向用户展示`1`或其他任何基本类型的唯一方式。不过对于结构体,`println!`应该用来输出的格式是不明确的,因为这有更多显示的可能性:是否需要逗号?需要打印出结构体的`{}`吗所有字段都应该显示吗因为这种不确定性Rust 不尝试猜测我们的意图所以结构体并没有提供一个`Display`的实现。
`println!` 宏能处理很多类型的格式,不过,`{}`,默认告诉 `println!` 使用称为 `Display` 的格式:直接提供给终端用户查看的输出。目前为止见过的基本类型都默认实现了 `Display`,因为它就是向用户展示 `1` 或其他任何基本类型的唯一方式。不过对于结构体,`println!` 应该用来输出的格式是不明确的,因为这有更多显示的可能性:是否需要逗号?需要打印出结构体的 `{}` 所有字段都应该显示吗因为这种不确定性Rust 不尝试猜测我们的意图所以结构体并没有提供一个 `Display` 实现。
但是如果我们继续阅读错误,将会发现这个有帮助的信息:
@ -155,22 +139,24 @@ note: `Rectangle` cannot be formatted with the default formatter; try using
`:?` instead if you are using a format string
```
让我们来试试!现在`println!`看起来像`println!("rect1 is {:?}", rect1);`这样。在`{}`中加入`:?`指示符告诉`println!`我们想要使用叫做`Debug`的输出格式。`Debug`是一个 trait它允许我们在调试代码时以一种对开发者有帮助的方式打印出结构体。
让我们来试试!现在 `println!` 宏调用看起来像 `println!("rect1 is {:?}", rect1);` 这样。在 `{}` 中加入 `:?` 指示符告诉 `println!` 我们想要使用叫做 `Debug` 的输出格式。`Debug` 是一个 trait它允许我们在调试代码时以一种对开发者有帮助的方式打印出结构体。
让我们试试运行这个变化...见鬼了。仍然能看到一个错误:
让我们试试运行这个变化。见鬼了!仍然能看到一个错误:
```text
error: the trait bound `Rectangle: std::fmt::Debug` is not satisfied
```
虽然编译器又一次给出了一个有帮助的信息!
不过编译器又一次给出了一个有帮助的信息!
```text
note: `Rectangle` cannot be formatted using `:?`; if it is defined in your
crate, add `#[derive(Debug)]` or manually implement it
```
Rust **确实**包含了打印出调试信息的功能,不过我们必须为结构体显式选择这个功能。为此,在结构体定义之前加上`#[derive(Debug)]`注解,如列表 5-6 所示:
Rust **确实** 包含了打印出调试信息的功能,不过我们必须为结构体显式选择这个功能。为此,在结构体定义之前加上 `#[derive(Debug)]` 注解,如列表 5-12 所示:
<span class="filename">文件名: src/main.rs</span>
```rust
#[derive(Debug)]
@ -186,8 +172,7 @@ fn main() {
}
```
<span class="caption">Listing 5-6: Adding the annotation to derive the `Debug`
trait and printing the `Rectangle` instance using debug formatting</span>
<span class="caption">列表 5-12增加注解来导出 `Debug` trait </span>
此时此刻运行程序,运行这个程序,不会有任何错误并会出现如下输出:
@ -195,7 +180,7 @@ trait and printing the `Rectangle` instance using debug formatting</span>
rect1 is Rectangle { length: 50, width: 30 }
```
好极了!这不是最漂亮的输出,不过它显示这个实例的所有字段,毫无疑问这对调试有帮助。如果想要输出再好看和易读一点,这对更大的结构体会有帮助,可以将`println!`的字符串中的`{:?}`替换为`{:#?}`。如果在这个例子中使用了美化的调试风格的话,输出会看起来像这样:
好极了!这不是最漂亮的输出,不过它显示这个实例的所有字段,毫无疑问这对调试有帮助。如果想要输出再好看和易读一点,可以将 `println!` 的字符串中的 `{:?}` 替换为 `{:#?} `,这对更大的结构体会有帮助。如果在这个例子中使用了 `{:#?}` 风格的话,输出会看起来像这样:
```text
rect1 is Rectangle {
@ -204,6 +189,6 @@ rect1 is Rectangle {
}
```
Rust 为我们提供了很多可以通过`derive`注解来使用的 trait他们可以为我们的自定义类型增加有益的行为。这些 trait 和行为在附录 C 中列出。第十章会涉及到如何通过自定义行为来实现这些 trait同时还有如何创建你自己的 trait。
Rust 为我们提供了很多可以通过 `derive` 注解来使用的 trait他们可以为我们的自定义类型增加有益的行为。这些 trait 和行为在附录 C 中列出。第十章会涉及到如何通过自定义行为来实现这些 trait同时还有如何创建你自己的 trait。
我们的`area`函数是非常明确的————它只是计算了长方形的面积。如果这个行为与`Rectangle`结构体再结合得更紧密一些就更好了,因为这明显就是`Rectangle`类型的行为。现在让我们看看如何继续重构这些代码,来将`area`函数协调进`Rectangle`类型定义的`area`**方法**中。
我们的 `area` 函数是非常特化的————它只是计算了长方形的面积。如果这个行为与 `Rectangle` 结构体再结合得更紧密一些就更好了,因为它不能用于其他类型。现在让我们看看如何继续重构这些代码,来将 `area` 函数协调进 `Rectangle` 类型定义的`area` **方法** 中。

View File

@ -4,13 +4,13 @@
> <br>
> commit 44bf3afd93519f8b0f900f21a5f2344d36e13448
**方法**与函数类似:他们使用`fn`关键和名字声明,他们可以拥有参数和返回值,同时包含一些代码会在某处被调用时执行。不过方法与函数是不同的,因为他们在结构体(或者枚举或者 trait 对象,将分别在第六章和第十七章讲解)的上下文中被定义,并且他们第一个参数总是`self`,它代表方法被调用的结构体的实例。
**方法** 与函数类似:他们使用 `fn` 关键和名字声明,可以拥有参数和返回值,同时包含一些代码会在某处被调用时执行。不过方法与函数是不同的,因为他们在结构体(或者枚举或者 trait 对象,将分别在第六章和第十七章讲解)的上下文中被定义,并且他们第一个参数总是` self`,它代表方法被调用的结构体的实例。
### 定义方法
让我们将获取一个`Rectangle`实例作为参数的`area`函数改写成一个定义于`Rectangle`结构体上的`area`方法,如列表 5-7 所示:
让我们将获取一个 `Rectangle` 实例作为参数的 `area` 函数改写成一个定义于 `Rectangle` 结构体上的 `area` 方法,如列表 5-13 所示:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
#[derive(Debug)]
@ -35,28 +35,23 @@ fn main() {
}
```
<span class="caption">Listing 5-7: Defining an `area` method on the `Rectangle`
struct</span>
<span class="caption">列表 5-13`Rectangle` 结构体上定义 `area` 方法</span>
<!-- Will add ghosting and wingdings here in libreoffice /Carol -->
为了使函数定义于 `Rectangle` 的上下文中,我们开始了一个 `impl` 块(`impl` 是 *implementation* 的缩写)。接着将函数移动到 `impl` 大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 `self`。然后在`main` 中将我们调用 `area` 方法并传递 `rect1` 作为参数的地方,改成使用 **方法语法***method syntax*)在 `Rectangle` 实例上调用 `area` 方法。方法语法获取一个实例并加上一个点号后跟方法名、括号以及任何参数。
为了使函数定义于`Rectangle`的上下文中,我们开始了一个`impl`块(`impl`是 *implementation* 的缩写)。接着将函数移动到`impl`大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成`self`。然后在`main`中将我们调用`area`方法并传递`rect1`作为参数的地方,改成使用**方法语法**在`Rectangle`实例上调用`area`方法。方法语法获取一个实例并加上一个点号后跟方法名、括号以及任何参数
`area` 的签名中,开始使用 `&self` 来替代 `rectangle: &Rectangle`,因为该方法位于 `impl Rectangle` 上下文中所以 Rust 知道 `self` 的类型是 `Rectangle`。注意仍然需要在 `self` 前面加上 `&`,就像 `&Rectangle` 一样。方法可以选择获取 `self` 的所有权,像我们这里一样不可变的借用 `self`,或者可变的借用 `self`,就跟其他别的参数一样
在`area`的签名中,开始使用`&self`来替代`rectangle: &Rectangle`,因为该方法位于`impl Rectangle` 上下文中所以 Rust 知道`self`的类型是`Rectangle`。注意仍然需要在`self`前面加上`&`,就像`&Rectangle`一样。方法可以选择获取`self`的所有权,像我们这里一样不可变的借用`self`,或者可变的借用`self`,就跟其他别的参数一样
这里选择 `&self` 跟在函数版本中使用 `&Rectangle` 出于同样的理由:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要能够在方法中改变调用方法的实例的话,需要将第一个参数改为 `&mut self`。通过仅仅使用 `self` 作为第一个参数来使方法获取实例的所有权,不过这是很少见的;这种技术通常用在当方法将 `self` 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例
这里选择`&self`跟在函数版本中使用`&Rectangle`出于同样的理由:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要能够在方法中改变调用方法的实例的话,需要将第一个参数改为`&mut self`。通过仅仅使用`self`作为第一个参数来使方法获取实例的所有权,不过这是很少见的;这通常用在当方法将`self`转换成别的实例的时候,同时我们想要防止调用者在转换之后使用原始的实例。
使用方法而不是函数,除了使用了方法语法和不需要在每个函数签名中重复`self`类型外,其主要好处在于组织性。我将某个类型实例能做的所有事情都一起放入`impl`块中,而不是让将来的用户在我们的代码中到处寻找`Rectangle`的功能。
<!-- PROD: START BOX -->
使用方法而不是函数,除了使用了方法语法和不需要在每个函数签名中重复` self` 类型之外,其主要好处在于组织性。我将某个类型实例能做的所有事情都一起放入 `impl` 块中,而不是让将来的用户在我们的代码中到处寻找 `Rectangle` 的功能。
> ### `->`运算符到哪去了?
>
> 像在 C++ 这样的语言中,有两个不同的运算符来调用方法:`.`直接在对象上调用方法,而`->`在一个对象的指针上调用方法这时需要先解引用dereference指针。换句话说如果`object`是一个指针,那么`object->something()`就像`(*object).something()`一样。
> 像在 C++ 这样的语言中,有两个不同的运算符来调用方法:`.` 直接在对象上调用方法,而 `->` 在一个对象的指针上调用方法这时需要先解引用dereference指针。换句话说如果 `object` 是一个指针,那么 `object->something()` 就像 `(*object).something()` 一样。
>
> Rust 并没有一个与`->`等效的运算符相反Rust 有一个叫**自动引用和解引用***automatic referencing and dereferencing*)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。
> Rust 并没有一个与 `->` 等效的运算符相反Rust 有一个叫 **自动引用和解引用***automatic referencing and dereferencing*)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。
>
> 这是它如何工作的:当使用`object.something()`调用方法时Rust 会自动增加`&`、`&mut`或`*`以便使`object`符合方法的签名。也就是说,这些代码是等同的:
> 这是它如何工作的:当使用 `object.something()` 调用方法时Rust 会自动增加 `&`、`&mut` `*` 以便使 `object` 符合方法的签名。也就是说,这些代码是等同的:
>
> ```rust
> # #[derive(Debug,Copy,Clone)]
@ -79,15 +74,13 @@ struct</span>
> (&p1).distance(&p2);
> ```
>
> 第一行看起来简洁的多。这种自动引用的行为之所以能行得通是因为方法有一个明确的接收者————`self`的类型。在给出接收者和方法名的前提下Rust 可以明确的计算出方法是仅仅读取(所以需要`&self`),做出修改(所以是`&mut self`)或者是获取所有权(所以是`self`。Rust 这种使得借用对方法接收者来说是隐式的做法是其所有权系统程序员友好性实现的一大部分。
<!-- PROD: END BOX -->
> 第一行看起来简洁的多。这种自动解引用的行为之所以能行得通是因为方法有一个明确的接收者————`self` 类型。在给出接收者和方法名的前提下Rust 可以明确的计算出方法是仅仅读取(`&self`),做出修改(`&mut self`)或者是获取所有权(`self`。Rust 这种使得借用对方法接收者来说是隐式的做法是其所有权系统程序员友好性实践的一大部分。
### 带有更多参数的方法
让我们更多的实践一下方法,通过为`Rectangle`结构体实现第二个方法。这回,我们让一个`Rectangle`的实例获取另一个`Rectangle`实例并返回`self`能否完全包含第二个长方形,如果能返回`true`若不能则返回`false`。当我们定义了`can_hold`方法,就可以运行列表 5-8 中的代码了:
让我们更多的实践一下方法,通过为 `Rectangle` 结构体实现第二个方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例并返回 `self` 能否完全包含第二个长方形,如果能返回 `true` 若不能则返回 `false`。一旦定义了 `can_hold` 方法,就可以运行列表 5-14 中的代码了:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
fn main() {
@ -100,19 +93,18 @@ fn main() {
}
```
<span class="caption">Listing 5-8: Demonstration of using the as-yet-unwritten
`can_hold` method</span>
<span class="caption">列表 5-14展示还未实现的 `can_hold` 方法的应用</span>
我们希望看到如下输出,因为`rect2`的长宽都小于`rect1`,而`rect3`比`rect1`要宽:
我们希望看到如下输出,因为 `rect2` 的长宽都小于 `rect1`,而 `rect3` `rect1` 要宽:
```text
Can rect1 hold rect2? true
Can rect1 hold rect3? false
```
因为我们想定义一个方法,所以它应该位于`impl Rectangle`块中。方法名是`can_hold`,并且它会获取另一个`Rectangle`的不可变借用作为参数。通过观察调用可以看出参数是什么类型的:`rect1.can_hold(&rect2)`传入了`&rect2`,它是一个`Rectangle`的实例`rect2`的不可变借用。这是可以理解的,因为我们只需要读取`rect2`(而不是写入,这意味着我们需要一个可变借用)而且希望`main`保持`rect2`的所有权这样就可以在调用这个方法后继续使用它。`can_hold`的返回值是一个布尔值,其实现会分别检查`self`的长宽是够都大于另一个`Rectangle`。让我们在列表 5-7 的`impl`块中增加这个新方法,如列表 5-9 所示:
因为我们想定义一个方法,所以它应该位于 `impl Rectangle` 块中。方法名是 `can_hold`,并且它会获取另一个 `Rectangle` 的不可变借用作为参数。通过观察调用位置的代码可以看出参数是什么类型的:`rect1.can_hold(&rect2)` 传入了 `&rect2`,它是一个 `Rectangle` 的实例 `rect2` 的不可变借用。这是可以理解的,因为我们只需要读取 `rect2`(而不是写入,这意味着我们需要一个可变借用)而且希望 `main` 保持 `rect2` 的所有权这样就可以在调用这个方法后继续使用它。`can_hold` 的返回值是一个布尔值,其实现会分别检查 `self` 的长宽是否都大于另一个 `Rectangle`。让我们在列表 5-13 的 `impl` 块中增加这个新方法,如列表 5-15 所示:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
# #[derive(Debug)]
@ -132,18 +124,15 @@ impl Rectangle {
}
```
<span class="caption">Listing 5-9: Implementing the `can_hold` method on
`Rectangle` that takes another `Rectangle` instance as an argument</span>
<span class="caption">列表 5-15`Rectangle` 上实现 `can_hold` 方法,它获取另一个 `Rectangle` 实例作为参数</span>
<!-- Will add ghosting here in libreoffice /Carol -->
如果结合列表 5-8 的`main`函数来运行,就会看到想要得到的输出!方法可以在`self`后增加多个参数,而且这些参数就像函数中的参数一样工作。
如果结合列表 5-14 的 `main` 函数来运行,就会看到想要得到的输出。方法可以在 `self` 后增加多个参数,而且这些参数就像函数中的参数一样工作。
### 关联函数
`impl`块的另一个好用的功能是:允许在`impl`块中定义**不**以`self`作为参数的函数。这被称为**关联函数***associated functions*),因为他们与结构体相关联。即便如此他们也是函数而不是方法,因为他们并不作用于一个结构体的实例。你已经使用过一个关联函数了:`String::from`。
`impl` 块的另一个有用的功能是:允许在 `impl` 块中定义 **不**`self` 作为参数的函数。这被称为 **关联函数***associated functions*),因为他们与结构体相关联。即便如此他们仍是函数而不是方法,因为他们并不作用于一个结构体的实例。你已经使用过一个关联函数了:`String::from`。
关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且用来作为长和宽,这样可以更轻松的创建一个正方形`Rectangle`而不必指定两次同样的值:
关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且同时用来作为长和宽,这样可以更轻松的创建一个正方形 `Rectangle` 而不必指定两次同样的值:
<span class="filename">Filename: src/main.rs</span>
@ -161,10 +150,36 @@ impl Rectangle {
}
```
使用结构体名和`::`语法来调用这个关联函数:比如`let sq = Rectangle::square(3);`。这个方法位于结构体的命名空间中:`::`语法用于关联函数和模块创建的命名空间,第七章会讲到后者。
使用结构体名和 `::` 语法来调用这个关联函数:比如 `let sq = Rectangle::square(3);`。这个方法位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间,第七章会讲到后者。
### 多个 `impl`
每个结构体都允许拥有多个 `impl` 块。例如,列表 5-15 等同于列表 5-16 的代码,这里每个方法有其自己的 `impl` 块:
```rust
# #[derive(Debug)]
# struct Rectangle {
# length: u32,
# width: u32,
# }
#
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.length > other.length && self.width > other.width
}
}
```
<span class="caption">列表 5-16使用多个 `impl` 块重写列表 5-15</span>
## 总结
结构体让我们可以在自己的范围内创建有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名他们来使得代码更清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。
结构体并不是创建自定义类型的唯一方法;让我们转向 Rust 的`enum`功能并为自己的工具箱再填一个工具。
结构体并不是创建自定义类型的唯一方法;让我们转向 Rust 的枚举功能并为自己的工具箱再填一个工具。

View File

@ -4,6 +4,6 @@
> <br>
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
本章介绍**枚举**,也被称作 *enums*。枚举允许你通过列举可能的值来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做`Option`,它代表一个值要么是一些值要么什么都不是。然后会讲到`match`表达式中的模式匹配如何使对枚举不同的值运行不同的代码变得容易。最后会涉及到`if let`,另一个简洁方便处理代码中枚举的结构。
本章介绍 **枚举***enumerations*,也被称作 *enums*。枚举允许你通过列举可能的值来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 `Option`,它代表一个值要么是一些值要么什么都不是。然后会讲到 `match` 表达式中的模式匹配如何使对枚举不同的值运行不同的代码变得容易。最后会涉及到 `if let`,另一个简洁方便处理代码中枚举的结构。
枚举是一个很多语言都有的功能不过不同语言中的功能各不相同。Rust 的枚举与像F#、OCaml 和 Haskell这样的函数式编程语言中的**代数数据类型***algebraic data types*)最为相似。
枚举是一个很多语言都有的功能不过不同语言中的功能各不相同。Rust 的枚举与像 F#、OCaml 和 Haskell 这样的函数式编程语言中的 **代数数据类型***algebraic data types*)最为相似。

View File

@ -2,13 +2,13 @@
> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-01-defining-an-enum.md)
> <br>
> commit e6d6caab41471f7115a621029bd428a812c5260e
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
让我们通过一用代码来表现的场景,来看看为什么这里枚举是有用的而且比结构体更合适。比如我们要处理 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*
可以通过在代码中定义一个 `IpAddrKind` 枚举来表现这个概念并列出可能的 IP 地址类型,`V4` `V6`。这被称为枚举的 **成员***variants*
```rust
enum IpAddrKind {
@ -17,11 +17,11 @@ enum IpAddrKind {
}
```
现在`IpAddrKind`就是一个可以在代码中使用的自定义类型了。
现在 `IpAddrKind` 就是一个可以在代码中使用的自定义类型了。
### 枚举值
可以像这样创建`IpAddrKind`两个不同成员的实例:
可以像这样创建 `IpAddrKind` 两个不同成员的实例:
```rust
# enum IpAddrKind {
@ -33,7 +33,7 @@ let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
```
注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。这么设计的益处是现在`IpAddrKind::V4`和`IpAddrKind::V6`是相同类型的:`IpAddrKind`。例如,接着我们可以定义一个函数来获取`IpAddrKind`
注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。这么设计的益处是现在 `IpAddrKind::V4` `IpAddrKind::V6` 是相同类型的:`IpAddrKind`。例如,接着可以定义一个函数来获取任何 `IpAddrKind`
```rust
# enum IpAddrKind {
@ -58,7 +58,7 @@ route(IpAddrKind::V4);
route(IpAddrKind::V6);
```
使用枚举甚至还有更多优势。进一步考虑一下我们的 IP 地址类型,目前没有一个储存实际 IP 地址**数据**的方法;只知道它是什么**类型**的。考虑到已经在第五章学习过结构体了,你可像列表 6-1 那样修改这个问题:
使用枚举甚至还有更多优势。进一步考虑一下我们的 IP 地址类型,目前没有一个储存实际 IP 地址 **数据** 的方法;只知道它是什么 **类型** 的。考虑到已经在第五章学习过结构体了,你可能会像列表 6-1 那样修改这个问题:
```rust
enum IpAddrKind {
@ -82,12 +82,11 @@ let loopback = IpAddr {
};
```
<span class="caption">Listing 6-1: Storing the data and `IpAddrKind` variant of
an IP address using a `struct`</span>
<span class="caption">列表 6-1将 IP 地址的数据和 `IpAddrKind` 成员储存在一个 `struct`</span>
这里我们定义了一个有两个字段的结构体`IpAddr``kind`字段是`IpAddrKind`(之前定义的枚举)类型的而`address`字段是`String`类型的。这里有两个结构体的实例。第一个,`home`,它的`kind`的值是`IpAddrKind::V4`与之相关联的地址数据是`127.0.0.1`。第二个实例,`loopback``kind`的值是`IpAddrKind`的另一个成员,`V6`,关联的地址是`::1`。我们使用了要给结构体来将`kind`和`address`打包在一起,现在枚举成员就与值相关联了。
这里我们定义了一个有两个字段的结构体 `IpAddr``kind` 字段是 `IpAddrKind`(之前定义的枚举)类型的而 `address` 字段是 `String` 类型的。这里有两个结构体的实例。第一个,`home`,它的 `kind` 的值是 `IpAddrKind::V4` 与之相关联的地址数据是 `127.0.0.1`。第二个实例,`loopback``kind` 的值是 `IpAddrKind` 的另一个成员,`V6`,关联的地址是 `::1`。我们使用了一个结构体来将 `kind``address` 打包在一起,现在枚举成员就与值相关联了。
我们可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。`IpAddr`枚举的新定义表明了`V4`和`V6`成员都关联了`String`值:
我们可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。`IpAddr` 枚举的新定义表明了 `V4` `V6` 成员都关联了 `String` 值:
```rust
enum IpAddr {
@ -102,7 +101,7 @@ let loopback = IpAddr::V6(String::from("::1"));
我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。
使用枚举而不是结构体还有另外一个优势每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将`V4`地址储存为四个`u8`值而`V6`地址仍然表现为一个`String`,这就不能使用结构体了。枚举可以轻易处理的这个情况:
使用枚举而不是结构体还有另外一个优势每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 `V4` 地址储存为四个 `u8` 值而 `V6` 地址仍然表现为一个 `String`,这就不能使用结构体了。枚举可以轻易处理的这个情况:
```rust
enum IpAddr {
@ -115,7 +114,7 @@ let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
```
这些代码展示了使用枚举来储存两种不同 IP 地址的几种可能的选择。然而,事实证明储存和编码 IP 地址实在是太常见了[以致标准库提供了一个可供使用的定义!][IpAddr]<!-- ignore -->让我们看看标准库如何定义`IpAddr`的:它正有着跟我们定义和使用的一样的枚举和成员,不过它将成员中的地址数据嵌入到了两个不同形式的结构体中,他们对不同的成员的定义是不同的:
这些代码展示了使用枚举来储存两种不同 IP 地址的几种可能的选择。然而,事实证明储存和编码 IP 地址实在是太常见了[以致标准库提供了一个可供使用的定义!][IpAddr]<!-- ignore -->让我们看看标准库如何定义 `IpAddr` 的:它正有着跟我们定义和使用的一样的枚举和成员,不过它将成员中的地址数据嵌入到了两个不同形式的结构体中,他们对不同的成员的定义是不同的:
[IpAddr]: https://doc.rust-lang.org/std/net/enum.IpAddr.html
@ -136,7 +135,7 @@ enum IpAddr {
这些代码展示了可以将任意类型的数据放入枚举成员中:例如字符串、数字类型或者结构体。甚至可以包含另一个枚举!另外,标准库中的类型通常并不比你设想出来的要复杂多少。
注意虽然标准库中包含一个`IpAddr`的定义,仍然可以创建和使用我们自己的定义而不会有冲突,因为我们并没有将标准库中的定义引入作用域。第七章会讲到如何导入类型。
注意虽然标准库中包含一个 `IpAddr` 的定义,仍然可以创建和使用我们自己的定义而不会有冲突,因为我们并没有将标准库中的定义引入作用域。第七章会讲到如何导入类型。
来看看列表 6-2 中的另一个枚举的例子:它的成员中内嵌了多种多样的类型:
@ -149,18 +148,16 @@ enum Message {
}
```
<span class="caption">Listing 6-2: A `Message` enum whose variants each store
different amounts and types of values</span>
<span class="caption">列表 6-2一个 `Message` 枚举,其每个成员都储存了不同数量和类型的值</span>
这个枚举有四个含有不同类型的成员:
* `Quit`没有关联任何数据。
* `Move`包含一个匿名结构体
* `Write`包含单独一个`String`。
* `ChangeColor`包含三个`i32`。
定义一个像列表 6-2 中的枚举类似于定义不同类型的结构体,除了枚举不使用`struct`关键字而且所有成员都被组合在一起位于`Message`下之外。如下这些结构体可以包含与之前枚举成员中相同的数据:
* `Quit` 没有关联任何数据。
* `Move` 包含一个匿名结构体
* `Write` 包含单独一个 `String`
* `ChangeColor` 包含三个 `i32`
定义一个像列表 6-2 中的枚举类似于定义不同类型的结构体,除了枚举不使用 `struct` 关键字而且所有成员都被组合在一起位于 `Message` 下之外。如下这些结构体可以包含与之前枚举成员中相同的数据:
```rust
struct QuitMessage; // unit struct
@ -172,9 +169,9 @@ struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct
```
不过如果我们使用不同的结构体,他们都有不同的类型,将不能轻易的定义一个获取任何这些信息类型的函数,正如可以使用列表 6-2 中定义的`Message`枚举那样因为他们是一个类型的。
不过如果我们使用不同的结构体,他们都有不同的类型,将不能轻易的定义一个获取任何这些信息类型的函数,正如可以使用列表 6-2 中定义的 `Message` 枚举那样因为他们是一个类型的。
结构体和枚举还有另一个相似点:就像可以使用`impl`来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们`Message`枚举上的叫做`call`的方法:
结构体和枚举还有另一个相似点:就像可以使用 `impl` 来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们 `Message` 枚举上的叫做 `call` 的方法:
```rust
# enum Message {
@ -194,17 +191,17 @@ let m = Message::Write(String::from("hello"));
m.call();
```
方法体使用了`self`来获取调用方法的值。这个例子中,创建了一个拥有类型`Message::Write("hello")`的变量`m`,而且这就是当`m.call()`运行时`call`方法中的`self`的值。
方法体使用了 `self` 来获取调用方法的值。这个例子中,创建了一个拥有类型 `Message::Write("hello")` 的变量 `m`,而且这就是当 `m.call()` 运行时 `call` 方法中的 `self` 的值。
让我们看看标准库中的另一个非常常见实用的枚举:`Option`。
让我们看看标准库中的另一个非常常见实用的枚举:`Option`。
### `Option`枚举和其相对空值的优势
### `Option` 枚举和其相对空值的优势
在之前的部分,我们看到了`IpAddr`枚举如何利用 Rust 的类型系统编码更多信息而不单单是程序中的数据。这一部分探索一个`Option`的案例分析,它是标准库定义的另一个枚举。`Option`类型应用广泛因为它编码了一个非常普遍的场景,就是一个值可能是某个值或者什么都不是。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
在之前的部分,我们看到了 `IpAddr` 枚举如何利用 Rust 的类型系统编码更多信息而不单单是程序中的数据。这一部分探索一个 `Option` 的案例分析,它是另一个标准库定义的枚举。`Option` 类型应用广泛因为它编码了一个非常普遍的场景,就是一个值可能是某个值或者什么都不是。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
编程语言的设计经常从其包含功能的角度考虑问题,但是从其所没有的功能的角度思考也很重要。Rust 并没有很多其他语言中有的空值功能。**空值***Null* )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
编程语言的设计经常从其包含功能的角度考虑问题,但是从其所排除在外的功能的角度思考也很重要。Rust 并没有很多其他语言中有的空值功能。**空值***Null* )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
在“Null References: The Billion Dollar Mistake”中Tony Hoarenull 的发明者,曾经说到:
“Null References: The Billion Dollar Mistake” Tony Hoarenull 的发明者,曾经说到:
> I call it my billion-dollar mistake. At that time, I was designing the first
> comprehensive type system for references in an object-oriented language. My
@ -215,13 +212,13 @@ m.call();
> crashes, which have probably caused a billion dollars of pain and damage in
> the last forty years.
>
> 我称之为我万亿美元的错误。当时,我在一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的应有都应该是绝对安全的。不过我未能抗拒引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数以万计美元的苦痛和伤害。
> 我称之为我万亿美元的错误。当时,我在一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的应有都应该是绝对安全的。不过我未能抗拒引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数以万计美元的苦痛和伤害。
空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性是无处不在的,非常容易出现这类错误。
然而,空值尝试表达的概念仍然是有意义的:空值是一个因为某种原因目前无效或缺失的值。
问题不在于具体的概念而在于特定的实现。为此Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是`Option<T>`,而且它[定义于标准库中][option]<!-- ignore -->,如下:
问题不在于具体的概念而在于特定的实现。为此Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 `Option<T>`,而且它[定义于标准库中][option]<!-- ignore -->,如下:
[option]: https://doc.rust-lang.org/std/option/enum.Option.html
@ -232,9 +229,9 @@ enum Option<T> {
}
```
`Option<T>`是如此有用以至于它甚至被包含在了 prelude 之中:不需要显式导入它。另外,它的成员也是如此:可以不需要`Option::`前缀来直接使用`Some`和`None`。即便如此`Option<T>`也仍是常规的枚举,`Some(T)`和`None`仍是`Option<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` 成员可以包含任意类型的数据。这里是一些包含数字类型和字符串类型 `Option` 值的例子:
```rust
let some_number = Some(5);
@ -243,12 +240,12 @@ let some_string = Some("a string");
let absent_number: Option<i32> = None;
```
如果使用`None`而不是`Some`,需要告诉 Rust `Option<T>`是什么类型的,因为编译器只通过`None`值无法推断出`Some`成员的类型。
如果使用 `None` 而不是 `Some`,需要告诉 Rust `Option<T>` 是什么类型的,因为编译器只通过 `None` 值无法推断出 `Some` 成员的类型。
当有一个`Some`值时,我们就知道存在一个值,而这个值保存在`Some`中。当有个`None`值时,在某种意义上它跟空值是相同的意义:并没有一个有效的值。那么,`Option<T>`为什么就比空值要好呢?
当有一个 `Some` 值时,我们就知道存在一个值,而这个值保存在 `Some` 中。当有个`None` 值时,在某种意义上它跟空值是相同的意义:并没有一个有效的值。那么,`Option<T>` 为什么就比空值要好呢?
简而言之,因为`Option<T>`和`T`(这里`T`可以是任何类型)是不同的类型,编译器不允许像一个被定义的有效的类型那样使用`Option<T>`。例如,这些代码不能编译,因为它尝试将`Option<i8>`与`i8`相比:
简而言之,因为 `Option<T>``T`(这里 `T` 可以是任何类型)是不同的类型,编译器不允许像一个被定义的有效的类型那样使用 `Option<T>`。例如,这些代码不能编译,因为它尝试将 `Option<i8>``i8` 相比:
```rust,ignore
let x: i8 = 5;
@ -259,7 +256,7 @@ let sum = x + y;
如果运行这些代码,将得到类似这样的错误信息:
```
```text
error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is
not satisfied
-->
@ -269,14 +266,14 @@ not satisfied
|
```
哇哦!事实上,错误信息意味着 Rust 不知道该如何将`Option<i8>`与`i8`相加。当在 Rust 中拥有一个像`i8`这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需判空。只有当使用`Option<i8>`(或者任何用到的类型)是需要担心可能没有一个值,而编译器会确保我们在使用值之前处理为空的情况。
哇哦!事实上,错误信息意味着 Rust 不知道该如何将 `Option<i8>` `i8` 相加。当在 Rust 中拥有一个像 `i8` 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需判空。只有当使用 `Option<i8>`(或者任何用到的类型)的时候需要担心可能没有一个值,而编译器会确保我们在使用值之前处理为空的情况。
换句话说,在对`Option<T>`进行`T`的运算之前必须转为`T`。通常这能帮助我们捕获空值最常见的问题之一:假设某值不为空但实际上为空的情况。
换句话说,在对 `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 之旅提供巨大的帮助。
那么当有一个 `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`表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。
总的来说,为了使用 `Option<T>` 值,需要编写处理每个成员的代码。我们想要一些代码只当拥有 `Some(T)` 值时运行,这些代码允许使用其中的 `T`。也希望一些代码当在 `None` 值时运行,这些代码并没有一个可用的 `T` 值。`match` 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。

View File

@ -1,14 +1,14 @@
## `match`控制流运算符
## `match` 控制流运算符
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-02-match.md)
> <br>
> commit 64090418c23d615facfe49a8d548ad9baea6b097
> commit 01dd4248621c2f510947592e47d16bdab9b14cf0
Rust 有一个叫做`match`的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及他们的作用。`match`的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及他们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
把`match`表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查`match`的每一个模式,并且在遇到第一个“符合”的模式时,值会进入相关联的代码块并在执行中被使用。
可以 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
因为刚刚提到了硬币,让我们用他们来作为一个使用`match`的例子!我们可以编写一个函数来获取一个未知的(美)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如列表 6-3 中所示:
因为刚刚提到了硬币,让我们用他们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如列表 6-3 中所示:
```rust
enum Coin {
@ -18,7 +18,7 @@ enum Coin {
Quarter,
}
fn value_in_cents(coin: Coin) -> i32 {
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
@ -28,18 +28,17 @@ fn value_in_cents(coin: Coin) -> i32 {
}
```
<span class="caption">Listing 6-3: An enum and a `match` expression that has
the variants of the enum as its patterns.</span>
<span class="caption">列表 6-3一个枚举和一个以枚举成员作为模式的 `match` 表达式</span>
拆开`value_in_cents`函数中的`match`来看。首先,我们列出`match`关键字后跟一个表达式,在这个例子中是`coin`的值。这看起来非常像`if`使用的表达式,不过这里有一个非常大的区别:对于`if`,表达式必须返回一个布尔值。而这里它可以是任何类型的。例子中的`coin`的类型是列表 6-3 中定义的`Coin`枚举。
拆开 `value_in_cents` 函数中的 `match` 来看。首先,我们列出 `match` 关键字后跟一个表达式,在这个例子中是 `coin` 的值。这看起来非常像 `if` 使用的表达式,不过这里有一个非常大的区别:对于 `if`,表达式必须返回一个布尔值。而这里它可以是任何类型的。例子中的 `coin` 的类型是列表 6-3 中定义的 `Coin` 枚举。
接下来是`match`的分支。一个分支有两个部分:一个模式和一些代码。第一个分支的模式是值`Coin::Penny`而之后的`=>`运算符将模式和将要运行的代码分开。这里的代码就仅仅是值`1`。每一个分支之间使用逗号分隔。
接下来是 `match` 的分支。一个分支有两个部分:一个模式和一些代码。第一个分支的模式是值 `Coin::Penny` 而之后的 `=>` 运算符将模式和将要运行的代码分开。这里的代码就仅仅是值 `1`。每一个分支之间使用逗号分隔。
当`match`表达式执行时,它将结果值按顺序与每一个分支的模式相比较,如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支,非常一个硬币分类器。可以拥有任意多的分支:列表 6-3 中的`match`有四个分支。
`match` 表达式执行时,它将结果值按顺序与每一个分支的模式相比较,如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支,非常类似一个硬币分类器。可以拥有任意多的分支:列表 6-3 中的 `match` 有四个分支。
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个`match`表达式的返回值。
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 `match` 表达式的返回值。
如果分支代码较短的话通常不使用大括号,正如列表 6-3 中的每个分支都只是返回一个值。如果想要在分支中运行多行代码,可以使用大括号。例如,如下代码在每次使用`Coin::Penny`调用时都会打印出“Lucky penny!”,同时仍然返回代码块最后的值,`1`
如果分支代码较短的话通常不使用大括号,正如列表 6-3 中的每个分支都只是返回一个值。如果想要在分支中运行多行代码,可以使用大括号。例如,如下代码在每次使用`Coin::Penny` 调用时都会打印出 “Lucky penny!”,同时仍然返回代码块最后的值,`1`
```rust
# enum Coin {
@ -49,7 +48,7 @@ the variants of the enum as its patterns.</span>
# Quarter,
# }
#
fn value_in_cents(coin: Coin) -> i32 {
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
@ -66,7 +65,7 @@ fn value_in_cents(coin: Coin) -> i32 {
匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值。
作为一个例子让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的`enum`,通过改变`Quarter`成员来包含一个`State`值,列表 6-4 中完成了这些修改:
作为一个例子让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的 `enum`,通过改变 `Quarter` 成员来包含一个 `State` 值,列表 6-4 中完成了这些修改:
```rust
#[derive(Debug)] // So we can inspect the state in a minute
@ -84,12 +83,11 @@ enum Coin {
}
```
<span class="caption">Listing 6-4: A `Coin` enum where the `Quarter` variant
also holds a `UsState` value</span>
<span class="caption">列表 6-4`Quarter` 成员也存放了一个 `UsState` 值的 `Coin` 枚举</span>
想象一下我们的一个朋友尝试收集所有 50 个州的 25 美分硬币。在根据硬币类型分类零钱的同时,也可以报告出每个 25 美分硬币所对应的州名称,这样如果我们的朋友没有的话,他可以把它加入收藏。
在这些代码的匹配表达式中,我们在匹配`Coin::Quarter`成员的分支的模式中增加了一个叫做`state`的变量。当匹配到`Coin::Quarter`时,变量`state`将会绑定 25 美分硬币所对应州的值。接着在那个分支的代码中使用`state`,如下:
在这些代码的匹配表达式中,我们在匹配 `Coin::Quarter` 成员的分支的模式中增加了一个叫做 `state` 的变量。当匹配到 `Coin::Quarter` 时,变量 `state` 将会绑定 25 美分硬币所对应州的值。接着在那个分支的代码中使用 `state`,如下:
```rust
# #[derive(Debug)]
@ -105,7 +103,7 @@ also holds a `UsState` value</span>
# Quarter(UsState),
# }
#
fn value_in_cents(coin: Coin) -> i32 {
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
@ -118,15 +116,15 @@ fn value_in_cents(coin: Coin) -> i32 {
}
```
如果调用`value_in_cents(Coin::Quarter(UsState::Alaska))``coin`将是`Coin::Quarter(UsState::Alaska)`。当将值与每个分支相比较时,没有分支会匹配直到遇到`Coin::Quarter(state)`。这时,`state`绑定的将会是值`UsState::Alaska`。接着就可以在`println!`表达式中使用这个绑定了,像这样就可以获取`Coin`枚举的`Quarter`成员中内部的州的值。
如果调用 `value_in_cents(Coin::Quarter(UsState::Alaska))``coin` 将是 `Coin::Quarter(UsState::Alaska)`。当将值与每个分支相比较时,没有分支会匹配直到遇到 `Coin::Quarter(state)`。这时,`state` 绑定的将会是值 `UsState::Alaska`。接着就可以在 `println!` 表达式中使用这个绑定了,像这样就可以获取 `Coin` 枚举的 `Quarter` 成员中内部的州的值。
### 匹配`Option<T>`
### 匹配 `Option<T>`
在之前的部分在使用`Option<T>`时我们想要从`Some`中取出其内部的`T`值;也可以像处理`Coin`枚举那样使用`match`处理`Option<T>`!与其直接比较硬币,我们将比较`Option<T>`的成员,不过`match`表达式的工作方式保持不变。
在之前的部分在使用 `Option<T>` 时我们想要从 `Some` 中取出其内部的 `T` 值;也可以像处理 `Coin` 枚举那样使用 `match` 处理 `Option<T>`!与其直接比较硬币,我们将比较 `Option<T>` 的成员,不过 `match` 表达式的工作方式保持不变。
比如我们想要编写一个函数,它获取一个`Option<i32>`并且如果其中有一个值,将其加一。如果其中没有值,函数应该返回`None`值并不尝试执行任何操作。
比如我们想要编写一个函数,它获取一个 `Option<i32>` 并且如果其中有一个值,将其加一。如果其中没有值,函数应该返回 `None` 值并不尝试执行任何操作。
编写这个函数非常简单,得益于`match`,它将看起来像列表 6-5 中这样:
得益于 `match`编写这个函数非常简单,它将看起来像列表 6-5 中这样:
```rust
fn plus_one(x: Option<i32>) -> Option<i32> {
@ -141,41 +139,39 @@ let six = plus_one(five);
let none = plus_one(None);
```
<span class="caption">Listing 6-5: A function that uses a `match` expression on
an `Option<i32>`</span>
<span class="caption">列表 6-5一个在 `Option<i32>` 上使用 `match` 表达式的函数</span>
#### 匹配`Some(T)`
更仔细的检查`plus_one`的第一行操作。当调用`plus_one(five)`时,`plus_one`函数体中的`x`将会是值`Some(5)`。接着将其与每个分支比较。
#### 匹配 `Some(T)`
让我们更仔细的检查 `plus_one` 的第一行操作。当调用 `plus_one(five)` 时,`plus_one` 函数体中的 `x` 将会是值 `Some(5)`。接着将其与每个分支比较。
```rust,ignore
None => None,
```
值`Some(5)`并不匹配模式`None`,所以继续进行下一个分支。
`Some(5)` 并不匹配模式 `None`,所以继续进行下一个分支。
```rust,ignore
Some(i) => Some(i + 1),
```
`Some(5)`与`Some(i)`匹配吗?为什么不呢!他们是相同的成员。`i`绑定了`Some`中包含的值,所以`i`的值是`5`。接着匹配分支的代码被执行,所以我们将`i`的值加一并返回一个含有值`6`的新`Some`。
`Some(5)` `Some(i)` 匹配吗?为什么不呢!他们是相同的成员。`i` 绑定了 `Some` 中包含的值,所以 `i` 的值是 `5`。接着匹配分支的代码被执行,所以我们将 `i` 的值加一并返回一个含有值 `6` 的新 `Some`
#### 匹配`None`
#### 匹配 `None`
接着考虑下列表 6-5 中`plus_one`的第二个调用,这里`x`是`None`。我们进入`match`并与第一个分支相比较。
接着考虑下列表 6-5 中 `plus_one` 的第二个调用,这里 `x` `None`。我们进入 `match` 并与第一个分支相比较。
```rust,ignore
None => None,
```
匹配上了!这里没有值来加一,所以程序结束并返回`=>`右侧的值`None`,因为第一个分支就匹配到了,其他的分支将不再比较。
匹配上了!这里没有值来加一,所以程序结束并返回 `=>` 右侧的值 `None`,因为第一个分支就匹配到了,其他的分支将不再比较。
将`match`与枚举相结合在很多场景中都是有用的。你会在 Rust 代码中看到很多这样的模式:`match`一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。这在一开始有点复杂,不过一旦习惯了,你会希望所有语言都拥有它!这一直是用户的最爱。
`match` 与枚举相结合在很多场景中都是有用的。你会在 Rust 代码中看到很多这样的模式:`match` 一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。这在一开始有点复杂,不过一旦习惯了,你会希望所有语言都拥有它!这一直是用户的最爱。
### 匹配是穷尽的
`match`还有另一方面需要讨论。考虑一下`plus_one`函数的这个版本:
`match` 还有另一方面需要讨论。考虑一下 `plus_one` 函数的这个版本:
```rust,ignore
fn plus_one(x: Option<i32>) -> Option<i32> {
@ -185,9 +181,9 @@ fn plus_one(x: Option<i32>) -> Option<i32> {
}
```
我们没有处理`None`的情况,所以这些代码会造成一个 bug。幸运的是这是一个 Rust 知道如何处理的 bug。如果尝试编译这段代码会得到这个错误
我们没有处理 `None` 的情况,所以这些代码会造成一个 bug。幸运的是这是一个 Rust 知道如何处理的 bug。如果尝试编译这段代码会得到这个错误
```
```text
error[E0004]: non-exhaustive patterns: `None` not covered
-->
|
@ -195,11 +191,12 @@ error[E0004]: non-exhaustive patterns: `None` not covered
| ^ pattern `None` not covered
```
Rust 知道我们没有覆盖所有可能的情况甚至知道那些模式被忘记了Rust 中的匹配是**穷尽的***exhaustive必须穷举到最后的可能性来使代码有效。特别的在这个`Option<T>`的例子中Rust 防止我们忘记明确的处理`None`的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。
### `_`通配符
Rust 知道我们没有覆盖所有可能的情况甚至知道那些模式被忘记了Rust 中的匹配是 **穷尽的***exhaustive必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。
Rust 也提供了一个模式用于不想列举出所有可能值的场景。例如,`u8`可以拥有 0 到 255 的有效的值,如果我们只关心 1、3、5 和 7 这几个值,就并不想必须列出 0、2、4、6、8、9 一直到 255 的值。所幸我们不必这么做:可以使用特殊的模式`_`替代:
### `_` 通配符
Rust 也提供了一个模式用于不想列举出所有可能值的场景。例如,`u8` 可以拥有 0 到 255 的有效的值,如果我们只关心 1、3、5 和 7 这几个值,就并不想必须列出 0、2、4、6、8、9 一直到 255 的值。所幸我们不必这么做:可以使用特殊的模式 `_` 替代:
```rust
let some_u8_value = 0u8;
@ -212,6 +209,6 @@ match some_u8_value {
}
```
`_`模式会匹配所有的值。通过将其放置于其他分支之后,`_`将会匹配所有之前没有指定的可能的值。`()`就是 unit 值,所以`_`的情况什么也不会发生。因此,可以说我们想要对`_`通配符之前没有列出的所有可能的值不做任何处理。
`_` 模式会匹配所有的值。通过将其放置于其他分支之后,`_` 将会匹配所有之前没有指定的可能的值。`()` 就是 unit 值,所以 `_` 的情况什么也不会发生。因此,可以说我们想要对 `_` 通配符之前没有列出的所有可能的值不做任何处理。
然而,`match`在只关心**一个**情况的场景中可能就有点啰嗦了。为此 Rust 提供了`if let`。
然而,`match` 在只关心 **一个** 情况的场景中可能就有点啰嗦了。为此 Rust 提供了`if let`。

View File

@ -1,10 +1,10 @@
## `if let`简单控制流
## `if let` 简单控制流
> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-03-if-let.md)
> <br>
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
`if let`语法让我们以一种不那么冗长的方式结合`if`和`let`,来处理匹配一个模式的值而忽略其他的值。考虑列表 6-6 中的程序,它匹配一个`Option<u8>`值并只希望当值是三时执行代码:
`if let` 语法让我们以一种不那么冗长的方式结合 `if` `let`,来处理匹配一个模式的值而忽略其他的值。考虑列表 6-6 中的程序,它匹配一个 `Option<u8>` 值并只希望当值为三时执行代码:
```rust
let some_u8_value = Some(0u8);
@ -14,12 +14,11 @@ match some_u8_value {
}
```
<span class="caption">Listing 6-6: A `match` that only cares about executing
code when the value is `Some(3)`</span>
<span class="caption">列表 6-6`match` 只关心当值为 `Some(3)` 时执行代码</span>
我们想要对`Some(3)`匹配进行操作不过不想处理任何其他`Some<u8>`值或`None`值。为了满足`match`表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上`_ => ()`,这样也要增加很多样板代码。
我们想要对 `Some(3)` 匹配进行操作不过不想处理任何其他 `Some<u8>` 值或 `None` 值。为了满足 `match` 表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上 `_ => ()`,这样也要增加很多样板代码。
不过我们可以使用`if let`这种更短的方式编写。如下代码与列表 6-6 中的`match`行为一致:
不过我们可以使用 `if let` 这种更短的方式编写。如下代码与列表 6-6 中的 `match` 行为一致:
```rust
# let some_u8_value = Some(0u8);
@ -28,13 +27,13 @@ if let Some(3) = some_u8_value {
}
```
`if let`获取通过`=`分隔的一个模式和一个表达式。它的工作方式与`match`相同,这里的表达式对应`match`而模式则对应第一个分支。
`if let` 获取通过 `=` 分隔的一个模式和一个表达式。它的工作方式与 `match` 相同,这里的表达式对应 `match` 而模式则对应第一个分支。
使用`if let`意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去`match`强制要求的穷尽性检查。`match`和`if let`之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
使用 `if let` 意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 `match` 强制要求的穷尽性检查。`match` `if let` 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
换句话说,可以认为`if let`是`match`的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。
换句话说,可以认为 `if let` `match` 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。
可以在`if let`中包含一个`else`。`else`块中的代码与`match`表达式中的`_`分支块中的代码相同,这样的`match`表达式就等同于`if let`和`else`。回忆一下列表 6-4 中`Coin`枚举的定义,它的`Quarter`成员包含一个`UsState`值。如果想要计数所有不是 25 美分的硬币的同时也报告 25 美分硬币所属的州,可以使用这样一个`match`表达式:
可以在 `if let` 中包含一个 `else`。`else` 块中的代码与 `match` 表达式中的 `_` 分支块中的代码相同,这样的 `match` 表达式就等同于 `if let` `else`。回忆一下列表 6-4 中 `Coin` 枚举的定义,它的 `Quarter` 成员包含一个 `UsState` 值。如果想要计数所有不是 25 美分的硬币的同时也报告 25 美分硬币所属的州,可以使用这样一个 `match` 表达式:
```rust
# #[derive(Debug)]
@ -57,7 +56,7 @@ match coin {
}
```
或者可以使用这样的`if let`和`else`表达式:
或者可以使用这样的 `if let` `else` 表达式:
```rust
# #[derive(Debug)]
@ -81,11 +80,11 @@ if let Coin::Quarter(state) = coin {
}
```
如果你的程序遇到一个使用`match`表达起来过于啰嗦的逻辑,记住`if let`也在你的 Rust 工具箱中。
如果你的程序遇到一个使用 `match` 表达起来过于啰嗦的逻辑,记住 `if let` 也在你的 Rust 工具箱中。
## 总结
现在我们涉及到了如何使用枚举来创建有一系列可列举值的自定义类型。我们也展示了标准库的`Option<T>`类型是如何帮助你利用类型系统来避免出错。当枚举值包含数据时,你可以根据需要处理多少情况来选择使用`match`或`if let`来获取并使用这些值。
现在我们涉及到了如何使用枚举来创建有一系列可列举值的自定义类型。我们也展示了标准库的 `Option<T>` 类型是如何帮助你利用类型系统来避免出错。当枚举值包含数据时,你可以根据需要处理多少情况来选择使用 `match` `if let` 来获取并使用这些值。
你的 Rust 程序现在能够使用结构体和枚举在自己的作用域内表现其内容了。在你的 API 中使用自定义类型保证了类型安全:编译器会确保你的函数只会得到它期望的类型的值。