mirror of
https://github.com/gnu4cn/rust-lang-zh_CN.git
synced 2025-01-30 06:00:13 +08:00
Fixed references across the book
This commit is contained in:
parent
65c7bd8c18
commit
465126d27f
@ -199,7 +199,7 @@ warning: `guessing_game` (bin "guessing_game") generated 1 warning
|
||||
|
||||
Rust 警告说不曾对返回自 `read_line` 的 `Result` 值进行使用,表示程序没有对可能的错误加以处理。
|
||||
|
||||
消除该警告信息的正确方式,就是要老老实实地编写错误处理代码,而在这个实例中,则只要在问题发生时,崩溃掉这个程序即可,因此这里就可以使用 `expect`。在 [第 9 章](Ch09_Error_Handling.md#recoverable-errors-with-result) 会掌握到如何从错误中恢复过来。
|
||||
消除该警告信息的正确方式,就是要老老实实地编写错误处理代码,而在这个实例中,则只要在问题发生时,崩溃掉这个程序即可,因此这里就可以使用 `expect`。在第 9 章的 [带有 Result 的可恢复错误](Ch09_Error_Handling.md#带有-result-的可恢复错误) 小节,会掌握到如何从错误中恢复过来。
|
||||
|
||||
## 使用 `println!` 的占位符将值打印出来
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
**Variables and Mutability**
|
||||
|
||||
就如在之前的 ["用变量保存值"](Ch02_Programming_a_Guessing_Game.md#storing-values-with-variables) 小节中所讲的那样,默认变量是不可变的。这是 Rust 所提供的,推动利用 Rust 赋予的安全和易于并发代码编写方式的众多措施之一(by default variables are immutable, this is one of many nudges Rust gives you to write your code in a way that takes advantage of the safety and easy concurrency that Rust offers)。尽管如此,还是有将变量作为可变的选项。下面就来搞清楚,为何 Rust 会鼓励偏向不可变,以及为何有时会希望不接受 Rust 的建议。
|
||||
就如在之前的 ["用变量保存值"](Ch02_Programming_a_Guessing_Game.md#使用变量保存那些值) 小节中所讲的那样,默认变量是不可变的。这是 Rust 所提供的,推动利用 Rust 赋予的安全和易于并发代码编写方式的众多措施之一(by default variables are immutable, this is one of many nudges Rust gives you to write your code in a way that takes advantage of the safety and easy concurrency that Rust offers)。尽管如此,还是有将变量作为可变的选项。下面就来搞清楚,为何 Rust 会鼓励偏向不可变,以及为何有时会希望不接受 Rust 的建议。
|
||||
|
||||
在变量为不可变时,一旦值被绑定到某个名字,那么就无法修改那个值了。为对此进行演示,就来通过使用 `cargo new variables` 在 `projects` 目录中生成一个新的名为 `variables` 的项目。
|
||||
|
||||
@ -89,7 +89,7 @@ x 的值为:6
|
||||
|
||||
与不可变变量类似, *常量(constants)* 是一些绑定到名字且不允许修改的值,但常量与变量之间,有些差异。
|
||||
|
||||
首先,是不允许在常量上使用 `mut` 关键字的。常量不光是默认不可变的 -- 他们一直都是不可变的。常量的声明用的是 `const` 关键字,而不是 `let` 关键字,同时值的类型 *必须* 被注解(be annotated)。在下一小节,[数据类型](#data-types),就会讲到类型和类型注解了,因此现在不要关心细节。只要明白必须始终对类型进行注解。
|
||||
首先,是不允许在常量上使用 `mut` 关键字的。常量不光是默认不可变的 -- 他们一直都是不可变的。常量的声明用的是 `const` 关键字,而不是 `let` 关键字,同时值的类型 *必须* 被注解(be annotated)。在下一小节,[数据类型](#数据类型),就会讲到类型和类型注解了,因此现在不要关心细节。只要明白必须始终对类型进行注解。
|
||||
|
||||
可在任何作用域,包括全局作用域中声明常量。而当在全局作用域中声明常量时,则会让那些代码中许多部分都需要知悉的值的常量,变得有用起来。
|
||||
|
||||
@ -261,7 +261,7 @@ fn main() {
|
||||
|
||||
> 关于 **整数溢出**
|
||||
>
|
||||
> 比方说有个类型为 `u8` 的、可保存 `0` 到 `255` 之间值的变量。在尝试将该变量修改为超出那个范围的某个值,比如 `256` 时,就会发生 *整型溢出(integer overflow)*,而整型溢出又可导致两种行为之一。在以调试模式进行程序编译时,Rust 就会包含整数溢出的检查,在发生了整数溢出时,就会导致程序进入 *错误(panic)* 状态。对于程序因错误而退出执行这种情况,Rust 使用了 猝死(paniking) 这个词语;在第 9 章中的 [带有 `panic!` 宏的不可恢复性错误](Ch09_Error_Handling.md#) 小节,将更深入地讨论到程序因错误而终止运行的情况。
|
||||
> 比方说有个类型为 `u8` 的、可保存 `0` 到 `255` 之间值的变量。在尝试将该变量修改为超出那个范围的某个值,比如 `256` 时,就会发生 *整型溢出(integer overflow)*,而整型溢出又可导致两种行为之一。在以调试模式进行程序编译时,Rust 就会包含整数溢出的检查,在发生了整数溢出时,就会导致程序进入 *错误(panic)* 状态。对于程序因错误而退出执行这种情况,Rust 使用了 猝死(paniking) 这个词语;在第 9 章中的 [带有 `panic!` 宏的不可恢复性错误](Ch09_Error_Handling.md#带-panic-的不可恢复错误) 小节,将更深入地讨论到程序因错误而终止运行的情况。
|
||||
>
|
||||
> 在以 `--release` 开关进行发布模式的编译时,Rust 就不会包含对引起程序终止运行的整数溢出的检查。这时若发生了溢出,Rust 就会执行 *二进制补码封装(two's complement wrapping)*。简而言之,对于比那种类型能保存的最大值还要大的值,就会被“回卷(wrap around)”到那种类型所能保存的最小值。以 `u8` 为例,值 `256` 就变成了 `0`,值 `257` 就变成了 `1`,如此等等。这样程序就不会死掉,而那个变量则会有着一个或许不是所期望的值。对整数溢出的回卷行为的依赖,被视为一种错误(Relying on integer overflow's wrapping behavior is considered an error)。
|
||||
>
|
||||
@ -341,7 +341,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
主要通过条件判断,来使用布尔值,比如在 `if` 表达式中。在 [控制流(Control Flow)](#control-flow) 小节,会讲到 Rust 中 `if` 表达式的工作方式。
|
||||
主要通过条件判断,来使用布尔值,比如在 `if` 表达式中。在 [控制流(Control Flow)](#控制流程control-flow) 小节,会讲到 Rust 中 `if` 表达式的工作方式。
|
||||
|
||||
### 字符类型
|
||||
|
||||
@ -359,7 +359,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
请注意,相比使用双引号来给字符串字面值进行值的指定,这里是以单引号来对这些 `char` 的字面值进行指定的。Rust 的 `char` 类型,有着四个字节的大小,而表示了 Unicode 的标量值,这就意味着他可以表示比仅仅 ASCII 要多得多的符号。像是重音字母(accented letters);中文、日语、韩语等等;emoji 符号;以及那些零宽度空格等等,在 Rust 中都是有效的 `char` 取值。Unicode 标量值的范围,是从 `U+0000` 到 `U+D7FF`,及 `U+E000` 到 `U+10FFFF`,包含边界值。不过,“字符(character)” 并非 Unicode 中的概念,因此对 “字符” 为何物的主观认识,可能与 Rust 中 `char` 的本质有所差别。在第 8 章中的 [用字符串存储 UTF-8 编码的文本](Ch08_Strings.md#storing-utf-8-encoded-text-with-strings) 小节,将对此话题进行讨论。
|
||||
请注意,相比使用双引号来给字符串字面值进行值的指定,这里是以单引号来对这些 `char` 的字面值进行指定的。Rust 的 `char` 类型,有着四个字节的大小,而表示了 Unicode 的标量值,这就意味着他可以表示比仅仅 ASCII 要多得多的符号。像是重音字母(accented letters);中文、日语、韩语等等;emoji 符号;以及那些零宽度空格等等,在 Rust 中都是有效的 `char` 取值。Unicode 标量值的范围,是从 `U+0000` 到 `U+D7FF`,及 `U+E000` 到 `U+10FFFF`,包含边界值。不过,“字符(character)” 并非 Unicode 中的概念,因此对 “字符” 为何物的主观认识,可能与 Rust 中 `char` 的本质有所差别。在第 8 章中的 [用字符串存储 UTF-8 编码的文本](Ch08_Strings.md#使用-string-存储-utf-8-编码的文本) 小节,将对此话题进行讨论。
|
||||
|
||||
## 复合类型
|
||||
|
||||
@ -432,7 +432,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
在希望数据分配在栈而不是堆(在 [第 4 章](Ch04_Understanding_Ownership.md#what-is-ownership) 将进一步讨论栈与堆)上时,或希望确保一直有着固定数目的元素时,数组就派上用场了。然而,数组不如矢量类型灵活。矢量是标准库所提供的,一种类似的集合类型,其大小 *可以* 变大或缩小。在不确定要使用数组,还是要使用矢量类型时,那多半就应该使用矢量了。[第 8 章](Ch08_Common_Collections.md#vectors) 对矢量类型进行了更详细的讨论。
|
||||
在希望数据分配在栈而不是堆(在 [第 4 章](Ch04_Understanding_Ownership.md#何谓所有权) 将进一步讨论栈与堆)上时,或希望确保一直有着固定数目的元素时,数组就派上用场了。然而,数组不如矢量类型灵活。矢量是标准库所提供的,一种类似的集合类型,其大小 *可以* 变大或缩小。在不确定要使用数组,还是要使用矢量类型时,那多半就应该使用矢量了。[第 8 章](Ch08_Common_Collections.md#使用矢量类型对值清单进行存储) 对矢量类型进行了更详细的讨论。
|
||||
|
||||
尽管如此,在了解了元素数目无需变化时,数组将更为有用。比如,在程序中正使用着各个月份名字时,由于是知道那将总是包含 12 个元素,因此就要使用数组而非矢量类型:
|
||||
|
||||
@ -838,7 +838,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Rust 还有另外一种注释,叫做文档注释,在第 14 章的 [将代码箱发布到 Crates.io](Ch14_More_about_Cargo_and_Crates.io.md#publishing-a-crate-tocrates-io) 中会对文档注释进行讨论。
|
||||
Rust 还有另外一种注释,叫做文档注释,在第 14 章的 [将代码箱发布到 Crates.io](Ch14_More_about_Cargo_and_Crates.io.md#将代码箱发布到-cratesio) 中会对文档注释进行讨论。
|
||||
|
||||
## 控制流程(Control Flow)
|
||||
|
||||
@ -864,7 +864,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
全部 `if` 表达式,都是以关键字 `if` 开头的,接着的是一个条件。在此示例中,那个条件就变量 `number` 是否小于 `5` 进行检查。是把要在条件为真时立即执行的代码块,放在条件之后、一对花括号里头。`if`表达式中与那些条件相关联的代码块,有时也叫做 *支臂(arms)*,这与在第 2 章的 [将猜数与秘密数字比较](Ch02_Programming_a_Guessing_Game.md#comparing-the-guess-to-the-secret-number) 小节中讨论过的 `match` 表达式中的支臂一样。
|
||||
全部 `if` 表达式,都是以关键字 `if` 开头的,接着的是一个条件。在此示例中,那个条件就变量 `number` 是否小于 `5` 进行检查。是把要在条件为真时立即执行的代码块,放在条件之后、一对花括号里头。`if`表达式中与那些条件相关联的代码块,有时也叫做 *支臂(arms)*,这与在第 2 章的 [将猜数与秘密数字比较](Ch02_Programming_a_Guessing_Game.md#将猜数与秘数相比较) 小节中讨论过的 `match` 表达式中的支臂一样。
|
||||
|
||||
作为可选项,还可以包含一个 `else` 表达式,即这里做的那样,从而给到程序一个替代性的、将在条件求解结果为 `false` 时执行的代码块。在未提供`else`表达式,且条件为 `false` 时,程序将直接跳过那个 `if` 代码块,而前往接下来的代码处。
|
||||
|
||||
@ -1075,7 +1075,7 @@ $ cargo run
|
||||
|
||||
其中的符号 `^C` 表示按下 `ctrl-c` 的地方。在那个 `^C` 之后,可能会也可能不会看到 `再次!` 被打印出来,取决于程序接收到中断信号时,代码在循环中的何处。
|
||||
|
||||
幸运的是,Rust 还提供了一种运用代码来跳出循环的方式。可在循环中放置 `break` 关键字,而告诉程序在何时结束执行这个循环。还记得在第 2 章的 [猜对数字后退出程序](Ch02_Programming_a_Guessing_Game.md#quitting-after-a-correct-guess) 小节,就在那个猜数游戏中这样做了,在通过猜到正确数字而赢得游戏时退出那个程序。
|
||||
幸运的是,Rust 还提供了一种运用代码来跳出循环的方式。可在循环中放置 `break` 关键字,而告诉程序在何时结束执行这个循环。还记得在第 2 章的 [猜对数字后退出程序](Ch02_Programming_a_Guessing_Game.md#猜对后的退出) 小节,就在那个猜数游戏中这样做了,在通过猜到正确数字而赢得游戏时退出那个程序。
|
||||
|
||||
在那个猜数游戏中,还使用了 `continue` 关键字,循环中的 `continue` 关键字,告诉程序去跳过循环本次迭代的其余全部代码,而前往下一次迭代。
|
||||
|
||||
|
@ -70,9 +70,9 @@ let s = "hello";
|
||||
|
||||
### `String` 类型
|
||||
|
||||
为了对所有权的那些规则进行演示,就需要比前面第 3 章的 ["数据类型"](Ch03_Common_Programming_Concepts.md#data-types) 小节中讲到那些类型,更为复杂一些的数据类型。前面讲到的那些类型,都是已知大小、可存储在栈上的,且在他们的作用域结束时会被弹出栈,在代码另一部分需要在不同作用域中用到同一值时,这些类型还可被快速而简单地复制,而构造出新的、独立实例。不过这里要审视的是存储在内存堆上的数据,进而探讨 Rust 是如何知晓,何时要清理这些内存堆上的数据,那么 `String` 类型就是极佳的示例了。
|
||||
为了对所有权的那些规则进行演示,就需要比前面第 3 章的 ["数据类型"](Ch03_Common_Programming_Concepts.md#数据类型) 小节中讲到那些类型,更为复杂一些的数据类型。前面讲到的那些类型,都是已知大小、可存储在栈上的,且在他们的作用域结束时会被弹出栈,在代码另一部分需要在不同作用域中用到同一值时,这些类型还可被快速而简单地复制,而构造出新的、独立实例。不过这里要审视的是存储在内存堆上的数据,进而探讨 Rust 是如何知晓,何时要清理这些内存堆上的数据,那么 `String` 类型就是极佳的示例了。
|
||||
|
||||
这里将着重于 `String` 类型与所有权有关的部分。这些方面同样适用于其他的、不论是由标准库还是自己创建的复合数据类型,complex data types。在 [第 8 章](Ch08_Common_Collections.md#strings) 将深入讲解 `String` 类型。
|
||||
这里将着重于 `String` 类型与所有权有关的部分。这些方面同样适用于其他的、不论是由标准库还是自己创建的复合数据类型,complex data types。在 [第 8 章](Ch08_Common_Collections.md#何为-string) 将深入讲解 `String` 类型。
|
||||
|
||||
前面咱们已经见到了一些字符串字面值,其中有个硬编码到程序里的字符串值。字符串字面值很方便,但对于那些打算使用文本的全部情形,他们却并不适合。一个原因是字符串字面值为不可变的。另一个原因则是,在编写代码时,并非每个字符串的值都是已知的:比如,假设要获取用户输入并存储下来呢?对于这样的情形,Rust 有着第二种字符串类型,即 `String`。这种类型对分配到内存堆上的数据加以管理,并因此而具备了存储在编译时数量未知文本的能力。使用 `String` 类型的 `from` 函数,就可以从字符串字面值,创建出一个 `String` 类型的值来,如下所示:
|
||||
|
||||
@ -81,7 +81,7 @@ let s = String::from("hello");
|
||||
// 变量 s 的类型为:String, 而此前字面值中的变量 s 的类型为:&str
|
||||
```
|
||||
|
||||
其中的双冒号(`::`)运算符,实现了将这个特定 `from` 函数,置于 `String` 类型的命名空间之下,而无需使用类似于 `string_from` 这种名字了。在第 5 章的 [方法语法](Ch05_Using_Structs_to_Structure_Related_Data.md#method-syntax) 小节,并在第 7 章的 [对模组树中的某个项目进行引用的路径](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#paths-for-referring-to-an-item-in-the-module-tree) 小节,对模组命名空间的介绍中,将对这种语法进行更多讲解。
|
||||
其中的双冒号(`::`)运算符,实现了将这个特定 `from` 函数,置于 `String` 类型的命名空间之下,而无需使用类似于 `string_from` 这种名字了。在第 5 章的 [方法语法](Ch05_Using_Structs_to_Structure_Related_Data.md#方法语法) 小节,并在第 7 章的 [对模组树中的某个项目进行引用的路径](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#用于引用目录树中项目的路径) 小节,对模组命名空间的介绍中,将对这种语法进行更多讲解。
|
||||
|
||||
这种字符串,*能* 被改变:
|
||||
|
||||
@ -137,7 +137,7 @@ let y = x;
|
||||
|
||||
或许能猜到这段代码正在完成的事情:“把值 `5` 绑定到变量 `x`;随后构造一份 `x` 中值的拷贝并将其绑定到变量 `y`。” 现在就有了两个变量,`x` 与 `y`,且他们都等于 `5`。由于整数是有着已知的、固定大小的简单值,因此这实际上就是正在发生的事情,且这两个 `5` 的值都是被压入到栈上的。
|
||||
|
||||
> **注**:这就是下面会讲到的 [栈上数据的拷贝,copy](#stack-only-data-copy) 情形。
|
||||
> **注**:这就是下面会讲到的 [栈上数据的拷贝,copy](#唯栈数据拷贝stack-only-data-copy) 情形。
|
||||
|
||||
|
||||
那么现在来看看 `String` 的版本:
|
||||
@ -270,7 +270,7 @@ println! ("x = {}, y = {}", x, y);
|
||||
|
||||
Rust 有着叫做 `Copy` 特质(the `Copy` trait, 在第 10 章将对特质,traits,进行更多的讲解)的,可放在像是整数这样的、存储于栈上的那些类型之上的一个特殊注解,a special annotation。在某个类型实现了 `Copy` 特质时,使用此类型的那些变量,就不会迁移,相反会轻而易举地被复制,从而在赋值给另一变量后,令到他们依然有效。
|
||||
|
||||
在某个类型或类型的任何部分带有 `Copy` 特质时,Rust 就不会再允许以 `Drop` 特质对其加以注解了。若某个类型需要在其值超出作用域后,还要进行某些特殊处理,而又将 `Copy` 注解添加到了那个类型,那么就会收到编译时错误(if the type needs something special to happen when the value goes out of scope and we add the `Copy` annotation to that type, we'll get a compile-time error)。要了解如何将 `Copy` 注解,添加到自己编写的类型而实现这个 `Copy` 特质,请参阅附录 C 中 [可派生特质(derivable traits)](Ch21_Appendix.md#derivable-traits)。
|
||||
在某个类型或类型的任何部分带有 `Copy` 特质时,Rust 就不会再允许以 `Drop` 特质对其加以注解了。若某个类型需要在其值超出作用域后,还要进行某些特殊处理,而又将 `Copy` 注解添加到了那个类型,那么就会收到编译时错误(if the type needs something special to happen when the value goes out of scope and we add the `Copy` annotation to that type, we'll get a compile-time error)。要了解如何将 `Copy` 注解,添加到自己编写的类型而实现这个 `Copy` 特质,请参阅附录 C 中 [可派生特质(derivable traits)](Ch21_Appendix.md#附录-c派生特质)。
|
||||
|
||||
那么到底哪些类型要实现 `Copy` 特质呢?可查阅给定类型的文档,来确定相应类型是否有实现 `Copy` 特质,不过作为一般规则,任何组别的简单标量值,any group of simple scalar values,都可实现 `Copy` 特质,以及不要求分配内存堆分配,或者其他形式资源的类型,也都可以实现 `Copy` 特质(any group of simple scalar values can implement `Copy`, and nothing that requires allocation or is some form of resource can implement `Copy`)。下面就是一些实现 `Copy` 特质的类型:
|
||||
|
||||
@ -418,7 +418,7 @@ fn calculate_length(s: &String) -> usize {
|
||||
*图 4-5:指向 `String s1` 的 `&String s` 图示*
|
||||
|
||||
|
||||
> 注意:这种经由使用 `&` (取地址)运算符,而得到的变量引用的反面,即为 *解引用,dereferencing*,解引用是以解引用运算符 `*` 达成的。在第 8 章中就会看到这个 [解引用运算符的使用](Ch08_Common_Collections.md#iterating-over-the-values-in-a-vector),而在第 15 章中,则会对解引用的细节加以讨论。
|
||||
> 注意:这种经由使用 `&` (取地址)运算符,而得到的变量引用的反面,即为 *解引用,dereferencing*,解引用是以解引用运算符 `*` 达成的。在第 8 章中就会看到这个 [解引用运算符的使用](Ch08_Common_Collections.md#对矢量中那些值的迭代),而在第 15 章中,则会对解引用的细节加以讨论。
|
||||
|
||||
来细看一下这里的函数调用:
|
||||
|
||||
@ -657,7 +657,7 @@ For more information about this error, try `rustc --explain E0106`.
|
||||
error: could not compile `ownership_demo` due to previous error
|
||||
```
|
||||
|
||||
此错误消息提到了一个这里还没有讲到特性:生命周期(lifetimes)。在第 10 章将 [详细讨论生命周期](Ch10_Generic_Types_and_Lifetimes.md#validating-references-with-lifetimes)。不过,忽略掉生命周期有关的那部分错误,那么该错误消息就真的包含了,这段代码为何是问题代码的关键原因:
|
||||
此错误消息提到了一个这里还没有讲到特性:生命周期(lifetimes)。在第 10 章将 [详细讨论生命周期](Ch10_Generic_Types_and_Lifetimes.md#使用生命周期对引用加以验证)。不过,忽略掉生命周期有关的那部分错误,那么该错误消息就真的包含了,这段代码为何是问题代码的关键原因:
|
||||
|
||||
```console
|
||||
this function's return type contains a borrowed value, but there is no value
|
||||
@ -745,7 +745,7 @@ for (i, &item) in bytes.iter().enumerate() {
|
||||
|
||||
在第 13 章,将讨论到迭代器的更多细节。而现在,明白 `iter` 是个返回集合中各个元素的方法,而那个 `enumerate` 则会将 `iter` 的结果进行封装进而将各个元素作为一个元组的组成部分,进行返回即可。自 `enumerate` 返回的元组第一个元素就是索引值,而第二个元素,则是到 `iter` 返回元素的索引。相比由代码编写者自己计算索引,这就要方便一点。
|
||||
|
||||
由于 `enumerate` 方法返回了一个元组,因此这里就可以使用模式,来解构那个元组。在 [第 6 章](Ch06_Enums_and_Pattern_Matching.md#patterns-that-bind-to-values),会对模式进行更多讨论。在那个 `for` 循环中,指定了一个有着用于那个元组中索引的 `i`,以及用于那个元组中单个字节的 `&item` 的模式。由于这里获得的是一个到从 `.iter().enumerate()` 获取元素的引用,因此在那个模式中使用了 `&` 运算符。
|
||||
由于 `enumerate` 方法返回了一个元组,因此这里就可以使用模式,来解构那个元组。在 [第 6 章](Ch06_Enums_and_Pattern_Matching.md#绑定到值的模式),会对模式进行更多讨论。在那个 `for` 循环中,指定了一个有着用于那个元组中索引的 `i`,以及用于那个元组中单个字节的 `&item` 的模式。由于这里获得的是一个到从 `.iter().enumerate()` 获取元素的引用,因此在那个模式中使用了 `&` 运算符。
|
||||
|
||||
在那个 `for` 循环内部,这里通过使用字节字面值语法(the byte literal syntax),就表示空格的字节进行了搜索。在找到空格时,就返回空格的位置。否则就通过使用 `s.len()` 返回该字符串的长度。
|
||||
|
||||
@ -839,7 +839,7 @@ let slice = &s[0..len];
|
||||
let slice = &s[..];
|
||||
```
|
||||
|
||||
> **注意**:这些字符串切片的范围索引值,必须出现于有效的 UTF-8 字符边界处。若在 UTF-8 多字节字符中间,尝试创建字符串切片,那么程序就会以错误退出。这里只是为介绍字符串切片目的,而假定本小节中只使用 ASCII 字符;在第 8 章的 [“以 `String` 类型值存储 UTF-8 编码的文本”](Ch08_Common_Collections.md#storing-utf-8-encoded-text-with-strings) 小节,有着对 UTF-8 字符串的更全面讨论。
|
||||
> **注意**:这些字符串切片的范围索引值,必须出现于有效的 UTF-8 字符边界处。若在 UTF-8 多字节字符中间,尝试创建字符串切片,那么程序就会以错误退出。这里只是为介绍字符串切片目的,而假定本小节中只使用 ASCII 字符;在第 8 章的 [“以 `String` 类型值存储 UTF-8 编码的文本”](Ch08_Common_Collections.md#使用-string-存储-utf-8-编码的文本) 小节,有着对 UTF-8 字符串的更全面讨论。
|
||||
|
||||
|
||||
对这全部字符串切片的情况了然在胸,那么下面就来将 `first_word` 重写为返回切片。表示 “字符串切片” 的类型,写做 `&str`:
|
||||
@ -937,7 +937,7 @@ fn first_word(s: &str) -> &str {
|
||||
|
||||
*清单 4-9:通过对 `s` 参数的类型使用字符串切片,对 `first_word` 函数进行改进*
|
||||
|
||||
在咱们有着某个字符串切片时,那么就可以直接传递那个字符串切片。而在咱们有着一个 `String` 时,则可传递该 `String` 的切片,或到这个 `String` 的引用。这种灵活性,是利用了 *强制引用解除,deref coercions* 特性,在第 15 章的 [函数与方法下的隐式强制解引用](Ch05_Smart_Pointers.md#implicit-deref-coercions-with-functions-and-methods) 小节,将讲到的一种特性。
|
||||
在咱们有着某个字符串切片时,那么就可以直接传递那个字符串切片。而在咱们有着一个 `String` 时,则可传递该 `String` 的切片,或到这个 `String` 的引用。这种灵活性,是利用了 *强制引用解除,deref coercions* 特性,在第 15 章的 [函数与方法下的隐式强制解引用](Ch05_Smart_Pointers.md#函数与方法下的隐式解引用强制转换) 小节,将讲到的一种特性。
|
||||
|
||||
这样定义出取字符串切片,而非到 `String` 值引用做参数的函数,令到这个 API 在不丢失任何功能的情况下,变得更为通用和有用:
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
## 结构体的定义及初始化
|
||||
|
||||
结构体与之前 [元组类型](Ch03_Common_Programming_Concepts.md#the-tuple-type) 小节中讨论过的元组数据结构类似,二者都保存着多个相关数据。和元组一样,结构体的各个数据片段可以是不同类型。与原则不同的是,在结构体中将给各个数据片段命名,如此各个值表示什么就清楚了。加上这些名字,就意味着相比于元组更为灵活了:不必为了给某个实例指定他的那些值,或要访问实例的那些值,而对实例数据的顺序有所依赖了。
|
||||
结构体与之前 [元组类型](Ch03_Common_Programming_Concepts.md#元组类型) 小节中讨论过的元组数据结构类似,二者都保存着多个相关数据。和元组一样,结构体的各个数据片段可以是不同类型。与原则不同的是,在结构体中将给各个数据片段命名,如此各个值表示什么就清楚了。加上这些名字,就意味着相比于元组更为灵活了:不必为了给某个实例指定他的那些值,或要访问实例的那些值,而对实例数据的顺序有所依赖了。
|
||||
|
||||
要定义出一个结构体,就要敲入关键字 `struct`,及整个结构体的名字。结构体名字,应对安排在一起的这些数据片段的意义加以描述。随后,就要这一对花括号里头,定义出各个数据片段的名称与类型,这些数据片段,就叫做 *字段(fields)*。比如,下面的清单 5-1 就给出了一个保存用户账号信息的结构体。
|
||||
|
||||
@ -133,7 +133,7 @@ fn main() {
|
||||
|
||||
清单 5-7 中的代码同样创建了在变量 `user2` 中,一个有着 `email` 的不同值,但有着来自 `user1` 的 `username`、`active` 及 `sign_in_count` 同样值。其中的 `..user1` 必须要在最后,这样来指明全部剩余字段都应从 `user1` 中的相应字段获取值,但对于其他字段值的指定,则可选择所要的任意字段,以任意顺序进行,而不论在结构体定义中这些字段的顺序为何(the `..user1` must come last to specify that any remaining fields should get their values from the corresponding fields in `user1`, but we can choose to specify values for as many fields as we want in any order, regardless of the order of the fields in the struct's definition)。
|
||||
|
||||
请注意结构体更新语法,像赋值一样使用了 `=`;这是由于结构体更新语法迁移了数据,就跟在之前的 ["变量与数据互动方式:迁移"](Ch04_Understanding_Ownership.md#ways-variables-and-data-interact-move) 小节中看到的那样。在此示例中,在创建了 `user2` 之后,由于变量 `user1` 中的 `username` 字段中的 `String` 值,已被迁移到 `user2` 中了,因此就再也不能使用变量 `user1` 了。若给到 `user2` 的 `email` 及 `username` 字段都是新的 `String` 值,而因此只使用来自 `user1` 的 `active` 和 `sign_in_count` 值,那么在创建了 `user2` 之后,`user1` 仍将是有效的。因为 `active` 和 `sign_in_count` 的类型,都是实现了 `Copy` 特质的类型,因此就会应用在 [唯栈数据:拷贝](Ch04_Understanding_Ownership.md#stack-only-data-copy) 小节中的行为表现。
|
||||
请注意结构体更新语法,像赋值一样使用了 `=`;这是由于结构体更新语法迁移了数据,就跟在之前的 ["变量与数据互动方式:迁移"](Ch04_Understanding_Ownership.md#变量与数据互操作方式之一迁移所有权) 小节中看到的那样。在此示例中,在创建了 `user2` 之后,由于变量 `user1` 中的 `username` 字段中的 `String` 值,已被迁移到 `user2` 中了,因此就再也不能使用变量 `user1` 了。若给到 `user2` 的 `email` 及 `username` 字段都是新的 `String` 值,而因此只使用来自 `user1` 的 `active` 和 `sign_in_count` 值,那么在创建了 `user2` 之后,`user1` 仍将是有效的。因为 `active` 和 `sign_in_count` 的类型,都是实现了 `Copy` 特质的类型,因此就会应用在 [唯栈数据:拷贝](Ch04_Understanding_Ownership.md#唯栈数据拷贝stack-only-data-copy) 小节中的行为表现。
|
||||
|
||||
|
||||
### 使用不带命名字段的元组结构体来创建不同类型
|
||||
@ -162,7 +162,7 @@ fn main() {
|
||||
|
||||
**Unit-Like Structs Without Any Fields**
|
||||
|
||||
还可以定义没有任何字段的结构体!由于这些没有任何字段的结构体,与曾在 [元组类型](Ch03_Common_Programming_Concepts.md#the-tuple-type) 小节提到过的单元类型 `()` 表现类似,因此他们叫做 *类单元结构体(unit-like structs)*。当需要在某类型上实现某个特质(trait),却又不希望将任何数据存储在那个类型自身里面时,类单元结构体就就有用(unit-like structs can be useful when you need to implement a trait on some type but don't have any data that you want to store in the type itself)。在第 10 章就会讨论到特质。下面是一个声明和初始化名为 `AlwaysEqual` 的单元结构体的示例:
|
||||
还可以定义没有任何字段的结构体!由于这些没有任何字段的结构体,与曾在 [元组类型](Ch03_Common_Programming_Concepts.md#元组类型) 小节提到过的单元类型 `()` 表现类似,因此他们叫做 *类单元结构体(unit-like structs)*。当需要在某类型上实现某个特质(trait),却又不希望将任何数据存储在那个类型自身里面时,类单元结构体就就有用(unit-like structs can be useful when you need to implement a trait on some type but don't have any data that you want to store in the type itself)。在第 10 章就会讨论到特质。下面是一个声明和初始化名为 `AlwaysEqual` 的单元结构体的示例:
|
||||
|
||||
```rust
|
||||
struct AlwaysEqual;
|
||||
@ -281,7 +281,7 @@ $ cargo run
|
||||
fn area(width: u32, height: u32) -> u32 {
|
||||
```
|
||||
|
||||
`area` 函数是要计算某个矩形面积的,但这里编写的该函数,有着两个参数,同时在这个程序中,并未清楚表明那两个参数是有联系的。将宽和高组织在一起,代码就会更具易读性,且更具可管理性。在第 3 章的 [元组类型](Ch03_Common_Programming_Concepts.md#the-tuple-type) 小节,就已讨论过一种可能那样做的方式:使用元组。
|
||||
`area` 函数是要计算某个矩形面积的,但这里编写的该函数,有着两个参数,同时在这个程序中,并未清楚表明那两个参数是有联系的。将宽和高组织在一起,代码就会更具易读性,且更具可管理性。在第 3 章的 [元组类型](Ch03_Common_Programming_Concepts.md#元组类型) 小节,就已讨论过一种可能那样做的方式:使用元组。
|
||||
|
||||
|
||||
### 以元组进行重构
|
||||
@ -451,7 +451,7 @@ rect1 为:Rectangle {
|
||||
|
||||
使用 `Debug` 格式化将某个值打印出来的另一种方式,就是使用 [`dbg!` 宏](https://doc.rust-lang.org/std/macro.dbg.html),这个 `dbg!` 宏会占据某个表达式的所有权,而将那个 `dbg!` 宏调用出现在代码中所在的文件与行号,与那个表达式的结果值一并打印出来,同时返回结果值的所有权(another way to print out a value using the [`dbg!` macro](https://doc.rust-lang.org/std/macro.dbg.html), which takes ownership of an expression, prints the file and line number of where that `dbg!` macro call occurs in your code along with the resulting value of that expression, and returns ownership of the value)。
|
||||
|
||||
> 注意:对 `dbg!` 宏的调用,会打印到标准错误控制台流(the standard error console stream, `stderr`),这与 `println!` 宏打印到标准输出控制台流(the standard output console stream, `stdout`)相反。在第 12 章中的 [将错误消息写到标准错误而非标准输出](Ch12_An_I_O_Project_Building_a_Command_Line_Program.md#writing-error-messages-to-standard-error-instead-of-standard-output) 小节,将讲到更多有关 `stderr` 与 `stdout` 的内容。
|
||||
> 注意:对 `dbg!` 宏的调用,会打印到标准错误控制台流(the standard error console stream, `stderr`),这与 `println!` 宏打印到标准输出控制台流(the standard output console stream, `stdout`)相反。在第 12 章中的 [将错误消息写到标准错误而非标准输出](Ch12_An_I_O_Project_Building_a_Command_Line_Program.md#把错误消息写到标准错误而非标准输出) 小节,将讲到更多有关 `stderr` 与 `stdout` 的内容。
|
||||
|
||||
以下是个其中对赋值给 `width` 字段,以及在变量 `rect1` 中的整个结构体的值感兴趣的示例:
|
||||
|
||||
@ -490,7 +490,7 @@ cargo run
|
||||
|
||||
这里就可以看到,输出的第一部分来自 `src/main.rs` 文件的第 10 行,正是对表达式 `30 * scale` 进行调式的地方,而该表达式的结果值即为 `60`(在整数原生值上实现的 `Debug` 格式化只打印他们的值)。在 `src/main.rs` 第 14 行上的 `dbg!` 调用,输出了 `rect1`,即那个 `Rectangle` 结构体的值。这个输出使用了 `Rectangle` 类型的良好 `Debug` 格式化。在尝试搞清楚代码在做什么时,这个 `dbg!` 宏真的会相当有用!
|
||||
|
||||
除 `Debug` 特质外,Rust 业已提供了数个与 `derive` 属性一起使用的其他特质,这些特质把有用的行为表现,添加到那些定制类型。Rust 提供的那些特质及其行为,在 [附录 C](Ch21_Appendix.md#c-derivable-traits) 小节中有列出。在第 10 章中,就会涉及到怎样去实现这些有着定制行为的特质,以及怎样创建自己的特质。除了 `derive` 之外,同样还有许多别的属性;有关属性的更多信息,请参阅 [Rust 参考手册的 “属性” 小节](https://doc.rust-lang.org/reference/attributes.html)。
|
||||
除 `Debug` 特质外,Rust 业已提供了数个与 `derive` 属性一起使用的其他特质,这些特质把有用的行为表现,添加到那些定制类型。Rust 提供的那些特质及其行为,在 [附录 C](Ch21_Appendix.md#附录-c派生特质) 小节中有列出。在第 10 章中,就会涉及到怎样去实现这些有着定制行为的特质,以及怎样创建自己的特质。除了 `derive` 之外,同样还有许多别的属性;有关属性的更多信息,请参阅 [Rust 参考手册的 “属性” 小节](https://doc.rust-lang.org/reference/attributes.html)。
|
||||
|
||||
这里的 `area` 函数,是相当专用的:他只会计算矩形的面积。由于 `area` 方法不会在其他任何类型上工作,因此将此行为与这里的 `Rectangle` 结构体更紧密的联系起来,就会变得有帮助。接下来就要看看,怎样通过将这个 `area` 函数,转变成一个定义在这里的 `Rectangle` 类型上的方法,而继续重构这段代码。
|
||||
|
||||
|
@ -491,7 +491,7 @@ Rust 还有一种在不愿使用捕获全部模式中的值时,可使用的一
|
||||
|
||||
由于这里在最后的支臂中,显式地忽略了全部其他值,因此该示例也是满足 `match` 表达式的穷尽要求的;这里并未忘记掉任何东西。
|
||||
|
||||
若再一次修改此游戏的规则,修改为在抛出即非三点也非七点的其他点数时,什么也不会发生,那么就可以通过使用单元值(即在 [元组类型](Ch03_Common_Programming_Concepts.md#the-tuple-type) 小节中讲到的那个空元组类型)作为该 `_` 支臂后的代码,来表达这样的游戏规则:
|
||||
若再一次修改此游戏的规则,修改为在抛出即非三点也非七点的其他点数时,什么也不会发生,那么就可以通过使用单元值(即在 [元组类型](Ch03_Common_Programming_Concepts.md#元组类型) 小节中讲到的那个空元组类型)作为该 `_` 支臂后的代码,来表达这样的游戏规则:
|
||||
|
||||
```rust
|
||||
let dice_roll = 9;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
在编写大型程序时,由于在头脑里对整个程序保持追踪已成为不可能,因此代码组织就尤为重要。通过将相关功能分组,并以截然不同的特性而将代码加以分离,就会搞清楚在哪里去找到实现了某个特定特性的代码,以及在哪里去修改某项特性的运作方式。
|
||||
|
||||
到目前为止,这里所编写的程序,都是在一个模组的一个文件中的。而随着项目的增长,就可以通过将项目分解为多个模组及多个文件,来对代码加以组织。一个代码包,可以包含多个二进制的代码箱,并可有选择地包含一个库代码箱。本章会涵盖到所有的这些技巧。对于那些极为大型、有着一套互相关联而共同演化的项目,Cargo 工具提供了工作区(workspaces)概念,关于工作区,将在第 14 章的 [Cargo 工作区](Ch14_More_about_Cargo_and_Crates_io.md#cargo-workspaces)中讲到。
|
||||
到目前为止,这里所编写的程序,都是在一个模组的一个文件中的。而随着项目的增长,就可以通过将项目分解为多个模组及多个文件,来对代码加以组织。一个代码包,可以包含多个二进制的代码箱,并可有选择地包含一个库代码箱。本章会涵盖到所有的这些技巧。对于那些极为大型、有着一套互相关联而共同演化的项目,Cargo 工具提供了工作区(workspaces)概念,关于工作区,将在第 14 章的 [Cargo 工作区](Ch14_More_about_Cargo_and_Crates_io.md#cargo-工作区)中讲到。
|
||||
|
||||
除了实现功能上的分组(grouping functionality)外,对功能实现细节的封装,还实现了更高层次上的代码重用:一旦实现了某个操作,其他代码就可以在无需掌握其实现原理的情况下,通过该代码的公共接口,对该实现代码加以调用。编写代码的方式,就定义了哪些部分是公开给其他代码使用的,哪些部分是私有实现细节而对其修改权力有所保留。这是对那些必须保留在头脑中细节实现数量,而有所限制的另一种方式(in addition to grouping functionality, encapsulating implementation details lets you reuse code at a higher level: once you've implemented an operation, other code can call that code via the code's pulic interface without knowing how the implementation works. The way you write code defines which part are public for other code to use and which parts are private implementation details that you reserve the right to change. This is another way to limit the amount of detail you have to keep in your head)。
|
||||
|
||||
@ -22,13 +22,13 @@
|
||||
这里将讲到的 Rust 模组系统的头几个部分,即为代码包与代码箱。
|
||||
|
||||
|
||||
*代码箱(a crate)* 是 Rust 编译器一次识别到的最低数量的代码(a *crate* is the smallest amount of code that the Rust compiler considers as a time)。即使运行 `rustc` 而非 `cargo`,并传递单个源码文件(就如同在第 1 章 [“编写并运行一个 Rust 程序”](Ch01_Getting_Started.md#writing-and-running-a-rust-program) 小节中曾干过的),编译器也会将那个文件,视为一个代码箱。代码箱可以包含一些模组,而这些模组则会被定义在其他的、与该代码箱一同被编译的一些文件中,就如同在接下来的小节中将看到的那样。
|
||||
*代码箱(a crate)* 是 Rust 编译器一次识别到的最低数量的代码(a *crate* is the smallest amount of code that the Rust compiler considers as a time)。即使运行 `rustc` 而非 `cargo`,并传递单个源码文件(就如同在第 1 章 [“编写并运行一个 Rust 程序”](Ch01_Getting_Started.md#hello-world) 小节中曾干过的),编译器也会将那个文件,视为一个代码箱。代码箱可以包含一些模组,而这些模组则会被定义在其他的、与该代码箱一同被编译的一些文件中,就如同在接下来的小节中将看到的那样。
|
||||
|
||||
代码箱有着两种形式:二进制代码箱(a binary crate),或库代码箱(a library crate)。*二进制代码箱(binary crates)* 是一些可编译为能够运行的可执行程序的一些程序,譬如命令行程序或服务器。二进制代码箱必须有着一个叫做 `main` 的、定义了在可执行文件运行时所发生事情的函数。到目前为止本书中创建的全部代码箱,都是二进制代码箱。
|
||||
|
||||
*库代码箱* 是没有 `main` 函数的,且他们不会编译到可执行文件。相反,他们定义的是计划在多个项目下共用的功能。比如在 [第二章](Ch02_Programming_a_Guessing_Game.md#generating-a-random-number) 中用到的 `rand` 代码箱,就提供了生成随机数的功能。在多数时候当 Rust 公民提到 “代码箱(crate)” 时,他们指的就是库代码箱,并且他们将 “代码箱(crate)” 与一般编程概念中的 “库(library)” 互换使用。
|
||||
*库代码箱* 是没有 `main` 函数的,且他们不会编译到可执行文件。相反,他们定义的是计划在多个项目下共用的功能。比如在 [第二章](Ch02_Programming_a_Guessing_Game.md#生成随机数) 中用到的 `rand` 代码箱,就提供了生成随机数的功能。在多数时候当 Rust 公民提到 “代码箱(crate)” 时,他们指的就是库代码箱,并且他们将 “代码箱(crate)” 与一般编程概念中的 “库(library)” 互换使用。
|
||||
|
||||
*代码箱根(crate root)* 是个 Rust 编译器开始之处的源文件,并构成了代码箱的根模组(the *crate root* is a source file that the Rust compiler starts from and makes up the root module of your crate. 后面在 [定义控制作用域和私有化的模组](#defining-modules-to-control-scope-and-privacy) 小节,将深入探讨到模组概念)。
|
||||
*代码箱根(crate root)* 是个 Rust 编译器开始之处的源文件,并构成了代码箱的根模组(the *crate root* is a source file that the Rust compiler starts from and makes up the root module of your crate. 后面在 [定义控制作用域和私有化的模组](#定义控制作用域和隐私的模组) 小节,将深入探讨到模组概念)。
|
||||
|
||||
*包(a package)* 即为提供了一套功能的一个或多个代码箱的捆绑包(a *package* is a bundle of one or more crates that provides a set of functionality)。包,包含了描述如何构建那些代码箱的一个 `Cargo.toml` 文件。Cargo 本身实际上就是,包含了前面曾用于构建代码的命令行工具二进制代码箱的包。Cargo 包还包含了一个该二进制代码箱所依赖的库代码箱。别的项目便可依靠这个 Cargo 库代码箱,来运用与 Cargo 命令行工具,所用到的同样逻辑。
|
||||
|
||||
@ -203,7 +203,7 @@ crate
|
||||
|
||||
这里将给出从定义在该代码箱根部的一个新函数 `eat_at_restaurant`,调用那个 `add_to_waitlist` 函数的两种方式。其中那些路径都是正确的,但由于存在别的问题,而将阻止此示例如往常那样编译。这里会稍加解释为何会这样。
|
||||
|
||||
其中的 `eat_at_restaurant` 函数,是这里的库代码箱公共 API 的一部分,因此要将其以 `pub` 关键字进行标记。在后面的 [使用 `pub` 关键字对路径进行暴露](#exposing-paths-with-the-pub-keyword) 小节,深入到更多有关 `pub` 关键字的细节。
|
||||
其中的 `eat_at_restaurant` 函数,是这里的库代码箱公共 API 的一部分,因此要将其以 `pub` 关键字进行标记。在后面的 [使用 `pub` 关键字对路径进行暴露](#使用-pub-关键字对路径进行暴露) 小节,深入到更多有关 `pub` 关键字的细节。
|
||||
|
||||
文件名:`src/lib.rs`
|
||||
|
||||
@ -676,7 +676,7 @@ pub fn eat_at_restaurant() {
|
||||
|
||||
在此项修改之前,外部代码必须通过使用路径 `restaurant::front_of_house::hosting::add_to_waitlist()`,来调用其中的 `add_to_waitlist` 函数。现在既然这个 `pub use` 已将该 `hosting` 模组,自根模组中重新导出,那么外部代码现在就可以使用 `restaurant::hosting::add_to_waitlist()` 路径了。
|
||||
|
||||
在所编写代码的内部结构,与调用代码的程序员们对该领域有着不同设想时,重导出就是有用的。比如,在这个饭馆的比喻中,运营该饭馆的人设想的是“前厅”与“后厨”。但造访饭馆的食客,或许不会用这样的词汇,来认识饭馆的这些部位。有了 `pub use`,就可以一种结构来编写代码,而以另一种结构将代码暴露出来。这样做就让这个库,对于在该库上编写代码的程序员,与调用这个库的程序员,均具备良好的组织。在第 14 章的 [“运用 `pub use` 导出便利的公共 API”](Ch14_More_about_Cargo_and_Crates_io.md#exporting-a-convenient-public-api-with-pub-use) 小节,将会看到另一个 `pub use` 的示例,并了解他是怎样影响到代码箱的文档。
|
||||
在所编写代码的内部结构,与调用代码的程序员们对该领域有着不同设想时,重导出就是有用的。比如,在这个饭馆的比喻中,运营该饭馆的人设想的是“前厅”与“后厨”。但造访饭馆的食客,或许不会用这样的词汇,来认识饭馆的这些部位。有了 `pub use`,就可以一种结构来编写代码,而以另一种结构将代码暴露出来。这样做就让这个库,对于在该库上编写代码的程序员,与调用这个库的程序员,均具备良好的组织。在第 14 章的 [“运用 `pub use` 导出便利的公共 API”](Ch14_More_about_Cargo_and_Crates_io.md#使用-pub-use-导出好用的公开-api) 小节,将会看到另一个 `pub use` 的示例,并了解他是怎样影响到代码箱的文档。
|
||||
|
||||
|
||||
### 使用外部 Rust 包
|
||||
@ -691,7 +691,7 @@ rand = `0.8.3`
|
||||
|
||||
将 `rand` 作为依赖项添加到 `Cargo.toml`,就告诉 Cargo,去 [crates.io](https://crates.io/) 下载那个 `rand` 包和任何的依赖项,而令到 `rand` 对此项目可用。
|
||||
|
||||
随后为了将 `rand` 的一些定义,带入到所编写的包中,这里添加了以代码箱名字,`rand`,开头,并列出了打算要带入到作用域中的那些条目的一个 `use` 行。回顾第 2 章中的 [“生成一个随机数”](Ch02_Programming_a_Guessing_Game.md#generating-a-random-number) 小节,那里就将那个 `Rng` 特质,带入到了作用域,并调用了 `rand::thread_rng` 函数:
|
||||
随后为了将 `rand` 的一些定义,带入到所编写的包中,这里添加了以代码箱名字,`rand`,开头,并列出了打算要带入到作用域中的那些条目的一个 `use` 行。回顾第 2 章中的 [“生成一个随机数”](Ch02_Programming_a_Guessing_Game.md#生成随机数) 小节,那里就将那个 `Rng` 特质,带入到了作用域,并调用了 `rand::thread_rng` 函数:
|
||||
|
||||
```rust
|
||||
use rand::Rng;
|
||||
@ -774,7 +774,7 @@ use std::collections::*;
|
||||
|
||||
这个 `use` 语句,将定义在 `std::collections` 中的全部公开项目,都带入到了当前作用域。在使用这个全局操作符时要当心!全局带入,会导致更难于分清哪些名字是作用域中,与在所编写程序中用到的名字,是在何处定义的。
|
||||
|
||||
通常是在测试时,要将正在测试的全部程序项目带入到 `tests` 模组,才使用这个全局操作符;在第 11 章中的 [怎样编写测试](Ch11_Writing_Automated_Tests.md#how-to-write-tests) 小节,就会讲到这个问题。在序曲模式(the prelude pattern)中,有时也会用到全局操作符:请参阅 [标准库文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes),了解有关更多序曲模式的知识。
|
||||
通常是在测试时,要将正在测试的全部程序项目带入到 `tests` 模组,才使用这个全局操作符;在第 11 章中的 [怎样编写测试](Ch11_Writing_Automated_Tests.md#怎样编写测试) 小节,就会讲到这个问题。在序曲模式(the prelude pattern)中,有时也会用到全局操作符:请参阅 [标准库文档](https://doc.rust-lang.org/std/prelude/index.html#other-preludes),了解有关更多序曲模式的知识。
|
||||
|
||||
|
||||
## 将模组拆分为不同文件
|
||||
@ -813,7 +813,7 @@ pub mod hosting {
|
||||
|
||||
*清单 7-22:文件 `src/front_of_house.rs` 中 `front_of_house` 模组内部的定义*
|
||||
|
||||
请注意只需在模组树中的某处,使用一次 `mod` 声明,而将某个文件的内容加载进来。一旦编译器获悉该文件是项目的一部分(且由已将那个 `mod` 语句放置于于何处,而掌握了该代码在模组树中所处的位置),项目中的其他文件,则应如同之前 [用于引用模组树中项目的路径](#paths-for-referring-to-an-item-in-the-module-tree) 小节中,曾讲到的到模组声明处的路径,来引用那个文件中的代码。也就是说,这里的 `mod` *并非* 其他编程语言有的那种 “include” 操作。
|
||||
请注意只需在模组树中的某处,使用一次 `mod` 声明,而将某个文件的内容加载进来。一旦编译器获悉该文件是项目的一部分(且由已将那个 `mod` 语句放置于于何处,而掌握了该代码在模组树中所处的位置),项目中的其他文件,则应如同之前 [用于引用模组树中项目的路径](#用于引用目录树中项目的路径) 小节中,曾讲到的到模组声明处的路径,来引用那个文件中的代码。也就是说,这里的 `mod` *并非* 其他编程语言有的那种 “include” 操作。
|
||||
|
||||
接下来,就要将那个 `hosting` 模组,提取到他自己的文件了。而由于 `hosting` 是 `front_of_house` ,而非根模组,的子模组,因此过程略有不同。这里将把 `hosting` 模组的文件,放在模组树中以其父辈命名的一个新目录中,此示例中即为 `src/front_of_house`。
|
||||
|
||||
|
@ -32,7 +32,7 @@ Rust 标准库中包含了几种名为 *集合(collections)* 的有用数据
|
||||
|
||||
请注意这里添加了个类型注解。由于这里没有往这个矢量插入任何值,因此 Rust 是不清楚这里要存储何种类别元素的。这是个重点。矢量值是使用泛型实现的;在后面第 10 章中,就会讲到怎样在自己的类型中使用泛型。而此刻,就要明白由标准库提供的这个 `Vec<T>` 可以保存任何类型。在创建保存特定类型的矢量时,可在尖括号里头指定那个类型。在清单 8-1 中,就告诉了 Rust,`v` 中的那个 `Vec<T>` 将保存 `i32` 类型的元素。
|
||||
|
||||
而更为常见的则是,会创建带有初始值的 `Vec<T>`,同时 Rust 就会推断出要存储的值类型,那么就很少会进行这样的类型注解。Rust 贴心地提供了 `vec!` 这个宏,这个宏就会创建出一个新的、保存给到他的那些值的矢量来。下面清单 8-2 就创建了一个新的、保存了值 `1`、`2` 与 `3` 的 `Vec<i32>`。之所以那个整数类型为 `i32`,是由于 `i32` 正是默认的整数类型,如同第 3 章的 ["数据类型"](Ch03_Common_Programming_Concepts.md#data-types) 中所讨论的那样。
|
||||
而更为常见的则是,会创建带有初始值的 `Vec<T>`,同时 Rust 就会推断出要存储的值类型,那么就很少会进行这样的类型注解。Rust 贴心地提供了 `vec!` 这个宏,这个宏就会创建出一个新的、保存给到他的那些值的矢量来。下面清单 8-2 就创建了一个新的、保存了值 `1`、`2` 与 `3` 的 `Vec<i32>`。之所以那个整数类型为 `i32`,是由于 `i32` 正是默认的整数类型,如同第 3 章的 ["数据类型"](Ch03_Common_Programming_Concepts.md#数据类型) 中所讨论的那样。
|
||||
|
||||
```rust
|
||||
let v = vec! [1, 2, 3];
|
||||
@ -181,7 +181,7 @@ error: could not compile `vec_demo` due to previous error
|
||||
|
||||
*清单 8-9:对实例中各个元素,进行可变引用的迭代*
|
||||
|
||||
要修改可变引用所指向的值,就必须使用 `*` 解引用操作符(the `*` dereference operator),在能够使用 `+=` 运算符之前,获取到 `i` 中的那个值。在后面第 15 章的 [“以解引用操作符,顺着指针找到值”](Ch15_Smart_Pointers.md#following-the-pointer-to-the-value) 小节,就会讲到这个解引用操作符。
|
||||
要修改可变引用所指向的值,就必须使用 `*` 解引用操作符(the `*` dereference operator),在能够使用 `+=` 运算符之前,获取到 `i` 中的那个值。在后面第 15 章的 [“以解引用操作符,顺着指针找到值”](Ch15_Smart_Pointers.md#跟随指针到其值) 小节,就会讲到这个解引用操作符。
|
||||
|
||||
|
||||
### 使用枚举存储多种类型
|
||||
@ -573,7 +573,7 @@ thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З
|
||||
|
||||
与矢量一样,哈希图是将他们的数据存储在内存堆上的。示例中的这个 `HashMap` 键的类型为 `String`,值的类型为 `i32`。与矢量类似,哈希图都是同质的(homogeneous):所有键都必须有着同样类型,且所有值也必须有着同样类型。
|
||||
|
||||
另一种构造哈希图的方式,即为通过使用迭代器,与元组矢量上的 `collect` 方法,而元组矢量中各个元组,则是由一个键与其值组成。在 [第 13 章的 “使用迭代器处理一系列的条目” 小节](Ch13_Functional_Language_Features_Iterators_and_Closures.md#processing-a-series-of-items-with-iterators),就会深入到迭代器的有关细节及其相关方法。`collect` 方法会将数据收集到包括 `HashMap` 在内数种集合类型中。比如,在将战队名字与初始得分放在两个单独矢量中时,那么就可以使用 `zip` 方法,来创建一个元组的迭代器,其中 `Blue` 就会与 `10` 结对,并以此类推。随后就可以使用 `collect` 方法类将那个元组迭代器,转换到一个哈希图了,如下清单 8-21 中所示。
|
||||
另一种构造哈希图的方式,即为通过使用迭代器,与元组矢量上的 `collect` 方法,而元组矢量中各个元组,则是由一个键与其值组成。在 [第 13 章的 “使用迭代器处理一系列的条目” 小节](Ch13_Functional_Language_Features_Iterators_and_Closures.md#使用迭代器对条目系列进行处理),就会深入到迭代器的有关细节及其相关方法。`collect` 方法会将数据收集到包括 `HashMap` 在内数种集合类型中。比如,在将战队名字与初始得分放在两个单独矢量中时,那么就可以使用 `zip` 方法,来创建一个元组的迭代器,其中 `Blue` 就会与 `10` 结对,并以此类推。随后就可以使用 `collect` 方法类将那个元组迭代器,转换到一个哈希图了,如下清单 8-21 中所示。
|
||||
|
||||
|
||||
```rust
|
||||
@ -650,7 +650,7 @@ error: could not compile `hashmap_demo` due to 2 previous errors
|
||||
|
||||
在对 `insert` 调用而导致 `field_name` 与 `field_value` 被迁移到那个哈希图中之后,这里就无法使用这两个变量了。
|
||||
|
||||
而在将到值的引用插入进哈希图时,这些值就不会被迁移进哈希图。对于这些引用所指向的值,则只要哈希图尚有效,那么他们便一直有效。在后面第 10 章的 [“以声明周期对引用有效性进行验证”](Ch10_Generic_Types_Traits_and_Lifetimes.md#validating-references-with-lifetimes) 小节中,将进一步讲到这些问题。
|
||||
而在将到值的引用插入进哈希图时,这些值就不会被迁移进哈希图。对于这些引用所指向的值,则只要哈希图尚有效,那么他们便一直有效。在后面第 10 章的 [“以声明周期对引用有效性进行验证”](Ch10_Generic_Types_Traits_and_Lifetimes.md#使用生命周期对引用加以验证) 小节中,将进一步讲到这些问题。
|
||||
|
||||
### 访问哈希图中的值
|
||||
|
||||
|
@ -112,13 +112,13 @@ note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose bac
|
||||
|
||||
在上面清单 9-2 里的输出中,回溯所指向到这里项目中行的第 6 行,就是导致问题的行:即 `src/main.rs` 的第 4 行。在不想要这个程序中止时,就应在首个提到了自己所编写文件的行,所指向的那个位置,开始排查。在之前的清单 9-1 中,那里有意编写了会中止的代码,而修正程序中止的办法,就是不要请求某个超出那个矢量索引范围的元素。而在今后代码中止时,就需要搞清楚代码是在对什么值进行何种操作,而导致了中止,以及代码应该怎么做。
|
||||
|
||||
在本章的 [要 `panic!` 或不要 `panic!`](#to-panic-or-not-to-panic) 小节,将回到 `panic!` 这个话题,并讨论在何时要用 `panic!`,何时不应使用 `panic!` 来处理不同错误情形。接下来,就会看看怎样使用 `Result`,从错误中恢复过来。
|
||||
在本章的 [要 `panic!` 或不要 `panic!`](#要-panic-还是不要-panic) 小节,将回到 `panic!` 这个话题,并讨论在何时要用 `panic!`,何时不应使用 `panic!` 来处理不同错误情形。接下来,就会看看怎样使用 `Result`,从错误中恢复过来。
|
||||
|
||||
## 带有 `Result` 的可恢复错误
|
||||
|
||||
多数错误都没有严重到要求程序整个地停止运行。某些时候,在某个函数失败时,必定是由于某种可易于解释进而加以响应的原因。比如在尝试打开某个文件,而因为要打开的文件不存在,那个操作失败了时,那么可能希望创建该文件,而不是中止这个进程。
|
||||
|
||||
回顾第二章中的 [处理潜在带有 `Result` 类型的程序失败](Ch02_Programming_a_Guessing_Game.md#handling-potential-failure-with-the-result-type) 小节,其中的 `Result` 枚举被定义为有两个变种,`Ok` 与 `Err`,如下所示:
|
||||
回顾第二章中的 [处理潜在带有 `Result` 类型的程序失败](Ch02_Programming_a_Guessing_Game.md#处理潜在的带有-result-的程序失效) 小节,其中的 `Result` 枚举被定义为有两个变种,`Ok` 与 `Err`,如下所示:
|
||||
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
@ -507,7 +507,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
*清单 9-12:将 `main` 修改为返回 `Result<(), E>`,就实现了在 `Result` 值上 `?` 操作符的使用*
|
||||
|
||||
这里的 `Box<dyn Error>` 类型,是个 *特质对象(trait object)*,在第 17 章中的 [“运用特质对象实现不同类型的值”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#using-trait-objects-that-allow-for-values-of-different-types) 小节,就会讲到这个特性。而现在,可将 `Box<dyn Error>` 理解为表示 “任何类别的错误”。由于 `?` 操作符允许将任何 `Err` 值及早返回,因此将 `?` 用在有着错误类型 `Box<dyn Error>` 的 `main` 函数中, 某个 `Result` 值上是允许的。即使这个 `main` 函数的函数体,将只会返回类型 `std::io::Error` 的那些错误,而经由指定 `Box<dyn Error>`,即使将返回其他错误的代码添加到 `main` 的函数体,该函数签名 `fn main() -> Result<(), Box<dyn Error>>` 仍将无误。
|
||||
这里的 `Box<dyn Error>` 类型,是个 *特质对象(trait object)*,在第 17 章中的 [“使用允许不同类型值的特质对象”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#使用允许不同类型值的特质对象) 小节,就会讲到这个特性。而现在,可将 `Box<dyn Error>` 理解为表示 “任何类别的错误”。由于 `?` 操作符允许将任何 `Err` 值及早返回,因此将 `?` 用在有着错误类型 `Box<dyn Error>` 的 `main` 函数中, 某个 `Result` 值上是允许的。即使这个 `main` 函数的函数体,将只会返回类型 `std::io::Error` 的那些错误,而经由指定 `Box<dyn Error>`,即使将返回其他错误的代码添加到 `main` 的函数体,该函数签名 `fn main() -> Result<(), Box<dyn Error>>` 仍将无误。
|
||||
|
||||
在 `main` 函数返回了一个 `Result<(), E>` 时,那么若 `main` 返回的是 `Ok(())`,则该可执行程序就会以值 `0` 退出,并在 `main` 返回 `Err` 值时,以非零值退出。C 语言编写的可执行程序,在退出时返回的是些整数:成功退出的程序返回整数 `0`,而出错的程序返回某些非 `0` 的整数。Rust 从可执行程序返回的也是整数,从而与此约定兼容。
|
||||
|
||||
@ -556,7 +556,7 @@ fn main () {
|
||||
|
||||
- 糟糕状态是某些不期望的东西,他们与偶发的东西相反,比如用户输入的错误格式数据;
|
||||
- 在此处之后的代码,需要依赖于不处在这种糟糕状态,而不是在接下来的每一步都检查这个问题;
|
||||
- 没有以自己所使用的类型,来编码该信息的好办法。在第 17 章的 [“将状态于行为编码为类型”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#encoding-states-and-behavior-as-types) 小节,就会贯穿一个这里所意指的示例。
|
||||
- 没有以自己所使用的类型,来编码该信息的好办法。在第 17 章的 [“将状态与行为编码为类型”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#) 小节,就会贯穿一个这里所意指的示例。
|
||||
|
||||
在有人调用到咱们的代码,并传入了无意义的值时,在可以的情况下,最好返回一个错误,这样库用户就可以确定在那样的情况下,他们打算做什么。然而在继续执行下去会不安全或有危害的情形中,那么最佳选择就会时调用 `panic!`,并警醒使用到咱们库的人他们代码中的错误,这样在他们开发过程中就可以修好那个代码错误。与此类似,在调用不在掌控中的外部代码,且该外部代码返回了无法修复的无效状态时,那么 `panic!` 通常就是恰当选择。
|
||||
|
||||
|
@ -730,7 +730,9 @@ error: could not compile `simple_blog` due to previous error
|
||||
经由这种完全按照面向对象模式下所定义的状态模式,来实现这种模式,咱们就没有利用上原本所能利用的 Rust 的全部优势。下面就来看看,咱们可对那个 `simple_blog` 能做出的,可将无效状态与无效状态转换,构造为编译时错误的一些改变。
|
||||
|
||||
|
||||
**将状态与行为当作类型编码,Encoding States and Behavior as Types**
|
||||
#### 将状态与行为当作类型编码
|
||||
|
||||
**Encoding States and Behavior as Types**
|
||||
|
||||
|
||||
咱们将给出如何对这种状态模式加以反思,以得到一套不同的权衡取舍。不同于对状态及状态的转换进行完全地封装,进而外部代码对他们一无所知,咱们将把那些状态编码为不同类型。于是乎,Rust 的类型检查系统,就将通过发出编译器报错,阻止在那些仅允许已发布帖子之处,使用草稿帖子的尝试。
|
||||
@ -791,7 +793,9 @@ impl DraftPost {
|
||||
`DraftPost` 结构体有着一个 `add_text` 方法,因此咱们就可以如同之前那样,把文本添加到 `content`,但请注意 `DraftPost` 并没有定义一个 `content` 方法!因此现在的程序确保了全部帖子都以草稿帖子开头,而草稿帖子并不会让他们的内容用于显示。任何绕过这些约束的尝试,都将导致编译器报错。
|
||||
|
||||
|
||||
**以到不同类型的转换,实现(状态的)转换,Implementing Transitions as Transformations into Different Types**
|
||||
#### 以到不同类型的转换,实现(状态的)转换
|
||||
|
||||
**Implementing Transitions as Transformations into Different Types**
|
||||
|
||||
|
||||
那么怎样来获取到某个已发布帖子呢?咱们是打算强化某个草稿帖子在其可被发布之前,必须被审阅和批准的规则。处于等待审阅状态的帖子,应仍然不显示任何内容。下面酒类通过添加另一结构体,`PendingReviewPost`、在 `DraftPost` 上定义出返回 `PendingReviewPost` 实例的 `request_review` 方法,以及在 `PendingReviewPost` 上定义出返回 `Post` 的 `approve` 方法,实现这些约束,如下清单 17-20 中所示:
|
||||
|
Loading…
Reference in New Issue
Block a user