Merge remote-tracking branch 'k/master'

This commit is contained in:
lcofjp 2017-12-07 08:48:10 +08:00
commit 6577b40b91
9 changed files with 37 additions and 37 deletions

View File

@ -6,7 +6,7 @@
我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。 我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,们被称作 **字段***field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体: 定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,们被称作 **字段***field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体:
```rust ```rust
struct User { struct User {
@ -19,7 +19,7 @@ struct User {
<span class="caption">示例 5-1`User` 结构体定义</span> <span class="caption">示例 5-1`User` 结构体定义</span>
一旦定义了结构体后为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 对的形式提供字段,其中 key 是字段的名字而 value 是需要储存在字段中的数据值。这时字段的顺序并不必要与在结构体中声明他们的顺序一致。换句话说,结构体的定义就像一个这个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户: 一旦定义了结构体后为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 对的形式提供字段,其中 key 是字段的名字value 是需要储存在字段中的数据值。实例中具体说明字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
```rust ```rust
# struct User { # struct User {
@ -83,7 +83,7 @@ fn build_user(email: String, username: String) -> User {
<span class="caption">示例 5-4`build_user` 函数获取 email 和用户名并返回 `User` 实例</span> <span class="caption">示例 5-4`build_user` 函数获取 email 和用户名并返回 `User` 实例</span>
不过,重复 `email` 字段与 `email` 变量的名字,同样的对于`username`,感觉有一点无趣。将函数参数起与结构体字段相同的名字是可以理解的,但是如果结构体有更多字段,重复们是十分烦人的。幸运的是,这里有一个方便的语法! 不过,重复 `email` 字段与 `email` 变量的名字,同样的对于`username`,感觉有一点无趣。将函数参数起与结构体字段相同的名字是可以理解的,但是如果结构体有更多字段,重复们是十分烦人的。幸运的是,这里有一个方便的语法!
### 变量与字段同名时的字段初始化语法 ### 变量与字段同名时的字段初始化语法
@ -178,11 +178,11 @@ let black = Color(0, 0, 0);
let origin = Point(0, 0, 0); let origin = Point(0, 0, 0);
``` ```
注意 `black``origin` 变量是不同的类型,因为们是不同的元组结构体的实例。我们定义的每一个结构体有着自己的类型,即使结构体中的字段有相同的类型。在其他方面,元组结构体类似我们在第三章提到的元组。 注意 `black``origin` 变量是不同的类型,因为们是不同的元组结构体的实例。我们定义的每一个结构体有着自己的类型,即使结构体中的字段有相同的类型。在其他方面,元组结构体类似我们在第三章提到的元组。
### 没有任何字段的类单元结构体 ### 没有任何字段的类单元结构体
我们也可以定义一个没有任何字段的结构体!他们被称为 **类单元结构体***unit-like structs*)因为他们类似于 `()`,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型内存储数据的时候发挥作用。我们将在第十章介绍 trait。 我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体***unit-like structs*)因为它们类似于 `()`,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型内存储数据的时候发挥作用。我们将在第十章介绍 trait。
> ## 结构体数据的所有权 > ## 结构体数据的所有权
> >

View File

@ -191,4 +191,4 @@ rect1 is Rectangle {
Rust 为我们提供了很多可以通过 `derive` 注解来使用的 trait他们可以为我们的自定义类型增加实用的行为。这些 trait 和行为在附录 C 中列出。第十章会涉及到如何通过自定义行为来实现这些 trait同时还有如何创建你自己的 trait。 Rust 为我们提供了很多可以通过 `derive` 注解来使用的 trait他们可以为我们的自定义类型增加实用的行为。这些 trait 和行为在附录 C 中列出。第十章会涉及到如何通过自定义行为来实现这些 trait同时还有如何创建你自己的 trait。
我们的 `area` 函数是非常特化的————它只是计算了长方形的面积。如果这个行为与 `Rectangle` 结构体再结合得更紧密一些就更好了,因为它不能用于其他类型。现在让我们看看如何继续重构这些代码,来将 `area` 函数协调进 `Rectangle` 类型定义的`area` **方法** 中。 我们的 `area` 函数是非常特化的它只是计算了长方形的面积。如果这个行为与 `Rectangle` 结构体再结合得更紧密一些就更好了,因为它不能用于其他类型。现在让我们看看如何继续重构这些代码,来将 `area` 函数协调进 `Rectangle` 类型定义的`area` **方法** 中。

View File

@ -4,11 +4,11 @@
> <br> > <br>
> commit 44bf3afd93519f8b0f900f21a5f2344d36e13448 > commit 44bf3afd93519f8b0f900f21a5f2344d36e13448
**方法** 与函数类似:他们使用 `fn` 关键字和名字声明,可以拥有参数和返回值,同时包含一些代码会在某处被调用时执行。不过方法与函数是不同的,因为他们在结构体(或者枚举或者 trait 对象,将分别在第六章和第十七章讲解)的上下文中被定义,并且他们第一个参数总是` self`,它代表方法被调用的结构体的实例。 **方法** 与函数类似:它们使用 `fn` 关键字和名字声明,可以拥有参数和返回值,同时包含一段该方法在某处被调用时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。
### 定义方法 ### 定义方法
让我们获取一个 `Rectangle` 实例作为参数的 `area` 函数改写成一个定义于 `Rectangle` 结构体上的 `area` 方法,如示例 5-13 所示: 让我们把前面实现的获取一个 `Rectangle` 实例作为参数的 `area` 函数改写成一个定义于 `Rectangle` 结构体上的 `area` 方法,如示例 5-13 所示:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -37,13 +37,13 @@ fn main() {
<span class="caption">示例 5-13`Rectangle` 结构体上定义 `area` 方法</span> <span class="caption">示例 5-13`Rectangle` 结构体上定义 `area` 方法</span>
为了使函数定义于 `Rectangle` 的上下文中,我们开始了一个 `impl` 块(`impl` 是 *implementation* 的缩写)。接着将函数移动到 `impl` 大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 `self`。然后在`main` 中将我们调用 `area` 方法并传递 `rect1` 作为参数的地方,改成使用 **方法语法***method syntax*)在 `Rectangle` 实例上调用 `area` 方法。方法语法获取一个实例并加上一个点号后跟方法名、括号以及任何参数。 为了使函数定义于 `Rectangle` 的上下文中,我们开始了一个 `impl` 块(`impl` 是 *implementation* 的缩写)。接着将函数移动到 `impl` 大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 `self`。然后在 `main` 中将我们先前调用 `area` 方法并传递 `rect1` 作为参数的地方,改成使用 **方法语法***method syntax*)在 `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` 的功能。 尽量使用方法替代函数,除了使用了方法语法和不需要在每个函数签名中重复 `self` 类型之外,其主要好处在于组织性。我将某个类型实例能做的所有事情都一起放入 `impl` 块中,而不是让将来的用户在我们的代码中到处寻找 `Rectangle` 的功能。
> ### `->`运算符到哪去了? > ### `->`运算符到哪去了?
> >
@ -74,11 +74,11 @@ fn main() {
> (&p1).distance(&p2); > (&p1).distance(&p2);
> ``` > ```
> >
> 第一行看起来简洁的多。这种自动解引用的行为之所以能行得通是因为方法有一个明确的接收者————`self` 类型。在给出接收者和方法名的前提下Rust 可以明确计算出方法是仅仅读取(`&self`),做出修改(`&mut self`)或者是获取所有权(`self`。Rust 这种使得借用对方法接收者来说是隐式的做法是其所有权系统程序员友好性实践的一大部分。 > 第一行看起来简洁的多。这种自动解引用的行为之所以能行得通是因为方法有一个明确的接收者————`self` 类型。在给出接收者和方法名的前提下Rust 可以明确计算出方法是仅仅读取(`&self`),做出修改(`&mut self`)或者是获取所有权(`self`。Rust 这种使得借用对方法接收者来说是隐式的做法是其所有权系统程序员友好性实践的一大部分。
### 带有更多参数的方法 ### 带有更多参数的方法
让我们更多的实践一下方法,通过为 `Rectangle` 结构体实现第二个方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例并返回 `self` 能否完全包含第二个长方形,如果能返回 `true`不能则返回 `false`。一旦定义了 `can_hold` 方法,就可以运行示例 5-14 中的代码了: 让我们更多的实践一下方法,通过为 `Rectangle` 结构体实现第二个方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例并返回 `self` 能否完全包含第二个长方形,如果能则返回 `true` ,如果不能则返回 `false`。一旦定义了 `can_hold` 方法,就可以运行示例 5-14 中的代码了:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -130,7 +130,7 @@ impl Rectangle {
### 关联函数 ### 关联函数
`impl` 块的另一个有用的功能是:允许在 `impl` 块中定义 **不**`self` 作为参数的函数。这被称为 **关联函数***associated functions*),因为他们与结构体相关联。即便如此他们仍是函数而不是方法,因为他们并不作用于一个结构体的实例。你已经使用过一个关联函数了:`String::from`。 `impl` 块的另一个有用的功能是:允许在 `impl` 块中定义 **不**`self` 作为参数的函数。这被称为 **关联函数***associated functions*),因为它们与结构体相关联。即便如此它们仍是函数而不是方法,因为它们并不作用于一个结构体的实例。你已经使用过一个关联函数了:`String::from`。
关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且同时用来作为长和宽,这样可以更轻松的创建一个正方形 `Rectangle` 而不必指定两次同样的值: 关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且同时用来作为长和宽,这样可以更轻松的创建一个正方形 `Rectangle` 而不必指定两次同样的值:
@ -150,7 +150,7 @@ impl Rectangle {
} }
``` ```
使用结构体名和 `::` 语法来调用这个关联函数:比如 `let sq = Rectangle::square(3);`。这个方法位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间,第七章会讲到后者 使用结构体名和 `::` 语法来调用这个关联函数:比如 `let sq = Rectangle::square(3);`。这个方法位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间,第七章会讲到模块
### 多个 `impl` ### 多个 `impl`
@ -180,6 +180,6 @@ impl Rectangle {
## 总结 ## 总结
结构体让我们可以在自己的范围内创建有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名他们来使得代码更清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。 结构体让我们可以在自己的范围内创建有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。
结构体并不是创建自定义类型的唯一方法;让我们转向 Rust 的枚举功能并为自己的工具箱再添一个工具。 结构体并不是创建自定义类型的唯一方法;让我们转向 Rust 的枚举功能并为自己的工具箱再添一个工具。

View File

@ -4,6 +4,6 @@
> <br> > <br>
> commit 4f2dc564851dc04b271a2260c834643dfd86c724 > commit 4f2dc564851dc04b271a2260c834643dfd86c724
本章介绍 **枚举***enumerations*),也被称作 *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

@ -4,9 +4,9 @@
> <br> > <br>
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9 > commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
让我们通过用代码来表现的场景,来看看为什么这里枚举是有用的而且比结构体更合适。比如我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准IPv4version four和 IPv6version six。这是我们的程序可能会遇到的 IP 地址的所有可能性:所以可以 **枚举** 出所有可能的值,这也正是它名字的由来。 让我们在一个实际场景中看看在这个特定场景下,使用枚举比使用结构体更合适。假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准IPv4version four和 IPv6version six。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 **枚举** 出所有可能的值,这也正是它名字的由来。
任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的而不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景因为枚举值只可能是其中一个成员。IPv4 和 IPv6 从根本上讲仍是 IP 地址,所以当代码在处理申请任何类型的 IP 地址的场景时应该把们当作相同的类型。 任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景因为枚举值只可能是其中一个成员。IPv4 和 IPv6 从根本上讲仍是 IP 地址,所以当代码在处理申请任何类型的 IP 地址的场景时应该把们当作相同的类型。
可以通过在代码中定义一个 `IpAddrKind` 枚举来表现这个概念并列出可能的 IP 地址类型,`V4` 和 `V6`。这被称为枚举的 **成员***variants* 可以通过在代码中定义一个 `IpAddrKind` 枚举来表现这个概念并列出可能的 IP 地址类型,`V4` 和 `V6`。这被称为枚举的 **成员***variants*
@ -33,7 +33,7 @@ let four = IpAddrKind::V4;
let six = IpAddrKind::V6; let six = IpAddrKind::V6;
``` ```
注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。这么设计的益处是现在 `IpAddrKind::V4``IpAddrKind::V6` 是相同类型的:`IpAddrKind`。例如,接着可以定义一个函数来获取任何 `IpAddrKind` 注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。这么设计的益处是现在 `IpAddrKind::V4``IpAddrKind::V6` 都是 `IpAddrKind` 类型的。例如,接着可以定义一个函数来获取任何 `IpAddrKind`
```rust ```rust
# enum IpAddrKind { # enum IpAddrKind {
@ -101,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 ```rust
enum IpAddr { enum IpAddr {
@ -114,7 +114,7 @@ let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::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 [IpAddr]: https://doc.rust-lang.org/std/net/enum.IpAddr.html
@ -169,7 +169,7 @@ struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct struct ChangeColorMessage(i32, i32, i32); // tuple struct
``` ```
不过如果我们使用不同的结构体,们都有不同的类型,将不能轻易的定义一个获取任何这些信息类型的函数,正如可以使用示例 6-2 中定义的 `Message` 枚举那样,因为们是一个类型的。 不过如果我们使用不同的结构体,们都有不同的类型,将不能轻易的定义一个获取任何这些信息类型的函数,正如可以使用示例 6-2 中定义的 `Message` 枚举那样,因为们是一个类型的。
结构体和枚举还有另一个相似点:就像可以使用 `impl` 来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们 `Message` 枚举上的叫做 `call` 的方法: 结构体和枚举还有另一个相似点:就像可以使用 `impl` 来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们 `Message` 枚举上的叫做 `call` 的方法:
@ -197,7 +197,7 @@ m.call();
### `Option` 枚举和其相对于空值的优势 ### `Option` 枚举和其相对于空值的优势
在之前的部分,我们看到了 `IpAddr` 枚举如何利用 Rust 的类型系统编码更多信息而不单单是程序中的数据。这一部分探索一个 `Option` 的案例分析,它是另一个标准库定义的枚举。`Option` 类型应用广泛因为它编码了一个非常普遍的场景,就是一个值可能是某个值或者什么都不是。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。 在之前的部分,我们看到了 `IpAddr` 枚举如何利用 Rust 的类型系统编码更多信息而不单单是程序中的数据。接下来我们分析一个 `Option` 的案例,`Option` 是标准库定义的另一个枚举。`Option` 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么是某个值要么什么都不是。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
编程语言的设计经常从其包含功能的角度考虑问题但是从其所排除在外的功能的角度思考也很重要。Rust 并没有很多其他语言中有的空值功能。**空值***Null* )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。 编程语言的设计经常从其包含功能的角度考虑问题但是从其所排除在外的功能的角度思考也很重要。Rust 并没有很多其他语言中有的空值功能。**空值***Null* )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
@ -212,7 +212,7 @@ m.call();
> crashes, which have probably caused a billion dollars of pain and damage in > crashes, which have probably caused a billion dollars of pain and damage in
> the last forty years. > the last forty years.
> >
> 我称之为我亿美元的错误。当时,我在为一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过我未能抗拒引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数以万计美元的苦痛和伤害。 > 我称之为我亿美元的错误。当时,我在为一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过我未能抗拒引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。
空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性是无处不在的,非常容易出现这类错误。 空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性是无处不在的,非常容易出现这类错误。
@ -229,7 +229,7 @@ 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` 值的例子:

View File

@ -4,11 +4,11 @@
> <br> > <br>
> commit 01dd4248621c2f510947592e47d16bdab9b14cf0 > commit 01dd4248621c2f510947592e47d16bdab9b14cf0
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。 Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
可以把 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。 可以把 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
因为刚刚提到了硬币,让我们用们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美帝)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示: 因为刚刚提到了硬币,让我们用们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美帝)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示:
```rust ```rust
enum Coin { enum Coin {
@ -155,7 +155,7 @@ None => None,
Some(i) => Some(i + 1), 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`
@ -192,7 +192,7 @@ error[E0004]: non-exhaustive patterns: `None` not covered
``` ```
Rust 知道我们没有覆盖所有可能的情况甚至知道那些模式被忘记了Rust 中的匹配是 **穷尽的***exhaustive必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。 Rust 知道我们没有覆盖所有可能的情况甚至知道那些模式被忘记了Rust 中的匹配是 **穷尽的***exhaustive*):必须穷举到最后的可能性来使代码有效。特别的在这个 `Option<T>` 的例子中Rust 防止我们忘记明确的处理 `None` 的情况,这使我们免于假设拥有一个实际上为空的值,这造成了之前提到过的价值亿万的错误。
### `_` 通配符 ### `_` 通配符

View File

@ -19,7 +19,7 @@
对于引用和`Box<T>`,借用规则的不可变性作用于编译时。对于`RefCell<T>`,这些不可变性作用于**运行时**。对于引用,如果违反这些规则,会得到一个编译错误。而对于`RefCell<T>`,违反这些规则会`panic!`。 对于引用和`Box<T>`,借用规则的不可变性作用于编译时。对于`RefCell<T>`,这些不可变性作用于**运行时**。对于引用,如果违反这些规则,会得到一个编译错误。而对于`RefCell<T>`,违反这些规则会`panic!`。
Rust 编译器执行的静态分析天生是保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(停机问题),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。 Rust 编译器执行的静态分析天生是保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(Halting Problem),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。
因为一些分析是不可能的Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不便,但不会带来灾难。`RefCell<T>`正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。 因为一些分析是不可能的Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不便,但不会带来灾难。`RefCell<T>`正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。