mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-14 04:41:49 +08:00
修正13到16章部分语法问题
This commit is contained in:
parent
76aa7d738a
commit
e6218be634
@ -15,7 +15,7 @@ test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
|
|||||||
|
|
||||||
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明它们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
|
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明它们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
|
||||||
|
|
||||||
对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象**(*zero-cost abstractions*)之一,它意味着抽象并不会引入运行时开销,它与本贾尼·斯特劳斯特卢普(C++ 的设计和实现者)在 “Foundations of C++”(2012)中所定义的 **零开销**(*zero-overhead*)如出一辙:
|
对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能的代码。迭代器是 Rust 的 **零成本抽象**(*zero-cost abstractions*)之一,它意味着抽象并不会引入运行时开销,它与本贾尼·斯特劳斯特卢普(C++ 的设计和实现者)在 “Foundations of C++”(2012)中所定义的 **零开销**(*zero-overhead*)如出一辙:
|
||||||
|
|
||||||
> In general, C++ implementations obey the zero-overhead principle: What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better.
|
> In general, C++ implementations obey the zero-overhead principle: What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better.
|
||||||
>
|
>
|
||||||
@ -25,7 +25,7 @@ test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
|
|||||||
>
|
>
|
||||||
> - 本贾尼·斯特劳斯特卢普 "Foundations of C++"
|
> - 本贾尼·斯特劳斯特卢普 "Foundations of C++"
|
||||||
|
|
||||||
作为另一个例子,这里有一些取自于音频解码器的代码。解码算法使用线性预测数学运算(linear prediction mathematical operation)来根据之前样本的线性函数预测将来的值。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫 `buffer` 的数据 slice、一个有 12 个元素的数组 `coefficients`、和一个代表位移位数的 `qlp_shift`。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简明的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:
|
作为另一个例子,这里有一些取自于音频解码器的代码。解码算法使用线性预测数学运算(linear prediction mathematical operation)来根据之前样本的线性函数预测将来的值。这些代码使用迭代器链对作用域中的三个变量进行某种数学计算:一个叫 `buffer` 的数据 slice、一个有 12 个元素的数组 `coefficients`、和一个代表位移位数的 `qlp_shift`。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简明的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
let buffer: &mut [i32];
|
let buffer: &mut [i32];
|
||||||
@ -44,12 +44,12 @@ for i in 12..buffer.len() {
|
|||||||
|
|
||||||
为了计算 `prediction` 的值,这些代码遍历了 `coefficients` 中的 12 个值,使用 `zip` 方法将系数与 `buffer` 的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移 `qlp_shift` 位。
|
为了计算 `prediction` 的值,这些代码遍历了 `coefficients` 中的 12 个值,使用 `zip` 方法将系数与 `buffer` 的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移 `qlp_shift` 位。
|
||||||
|
|
||||||
像音频解码器这样的程序通常最看重计算的性能。这里,我们创建了一个迭代器,使用了两个适配器,接着消费了其值。那么这段 Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环:Rust 知道这里会迭代 12 次,所以它“展开”(unroll)了循环。展开是一种移除循环控制代码的开销,并将循环迭代转换为程序中的重复的代码的优化。
|
像音频解码器这样的程序通常最看重计算的性能。这里,我们创建了一个迭代器,使用了两个适配器,接着消费了其值。那么这段 Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环:Rust 知道这里会迭代 12 次,所以它“展开”(unroll)了循环。展开是一种将循环迭代转换为重复代码,并移除循环控制代码开销的代码优化技术。
|
||||||
|
|
||||||
所有的系数都被储存在了寄存器中,这意味着访问它们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。现在知道这些了,请放心大胆的使用迭代器和闭包吧!它们使得代码看起来更高级,但并不为此引入运行时性能损失。
|
所有的系数都被储存在了寄存器中,这意味着访问它们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。现在知道这些了,请放心大胆的使用迭代器和闭包吧!它们使得代码看起来更高级,但并不为此引入运行时性能损失。
|
||||||
|
|
||||||
## 总结
|
## 总结
|
||||||
|
|
||||||
闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。它们对 Rust 以底层的性能来明确的表达高级概念的能力有很大贡献。闭包和迭代器的实现达到了不影响运行时性能的程度。这正是 Rust 竭力提供零成本抽象的目标的一部分。
|
闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。它们对 Rust 以高性能来明确的表达高级概念的能力有很大贡献。闭包和迭代器的实现达到了不影响运行时性能的程度。这正是 Rust 竭力提供零成本抽象的目标的一部分。
|
||||||
|
|
||||||
现在我们改进了我们 I/O 项目的(代码)表现力,让我们看一看更多 `cargo` 的功能,它们将帮助我们准备好将项目分享给世界。
|
现在我们改进了 I/O 项目的(代码)表现力,那么让我们来看看 `cargo` 的更多功能,这些功能将帮助我们将项目分享给世界。
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 44e31f9f304e0cd9ace01045d17a2aa01a449528
|
> commit 44e31f9f304e0cd9ace01045d17a2aa01a449528
|
||||||
|
|
||||||
在 Rust 中 **发布配置**(*release profiles*)是预定义的、可定制的带有不同选项的配置,它们允许程序员更灵活地控制代码编译的多种选项。每一个配置都彼此相互独立。
|
在 Rust 中 **发布配置**(*release profiles*)文件是预定义和可定制的,它们包含不同的配置,允许程序员更灵活地控制代码编译的多种选项。每一个配置都相互独立。
|
||||||
|
|
||||||
Cargo 有两个主要的配置:运行 `cargo build` 时采用的 `dev` 配置和运行 `cargo build --release` 的 `release` 配置。`dev` 配置被定义为开发时的好的默认配置,`release` 配置则有着良好的发布构建的默认配置。
|
Cargo 有两个主要的配置:运行 `cargo build` 时采用的 `dev` 配置和运行 `cargo build --release` 的 `release` 配置。`dev` 配置为开发定义了良好的默认配置,`release` 配置则为发布构建定义了良好的默认配置。
|
||||||
|
|
||||||
这些配置名称可能很眼熟,因为它们出现在构建的输出中:
|
这些配置名称可能很眼熟,因为它们出现在构建的输出中:
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ Rust 和 Cargo 有一些帮助他人更方便地找到和使用你发布的包
|
|||||||
|
|
||||||
### 编写有用的文档注释
|
### 编写有用的文档注释
|
||||||
|
|
||||||
准确的包文档有助于其他用户理解如何以及何时使用它们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用两斜杠 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释**(_documentation comments_),它们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,它们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate,而不是它是如何被 **实现** 的。
|
准确的包文档有助于其他用户理解如何以及何时使用它们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用双斜杠 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释**(_documentation comments_),它们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,它们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate,而不是它是如何被 **实现** 的。
|
||||||
|
|
||||||
文档注释使用三斜杠 `///` 而不是两斜杆以支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释,
|
文档注释使用三斜杠 `///` 而不是双斜杠以支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释,
|
||||||
|
|
||||||
<span class="filename">文件名:src/lib.rs</span>
|
<span class="filename">文件名:src/lib.rs</span>
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ $ cargo new adder
|
|||||||
└── target
|
└── target
|
||||||
```
|
```
|
||||||
|
|
||||||
工作空间在顶级目录有一个 *target* 目录;`adder` 并没有自己的 *target* 目录。即使进入 *adder* 目录运行 `cargo build`,构建结果也位于 *add/target* 而不是 *add/adder/target*。工作空间中的 crate 之间相互依赖。如果每个 crate 有其自己的 *target* 目录,为了在自己的 *target* 目录中生成构建结果,工作空间中的每一个 crate 都不得不相互重新编译其他 crate。通过共享一个 *target* 目录,工作空间可以避免其他 crate 多余的重复构建。
|
工作空间在顶级目录有一个 *target* 目录;`adder` 并没有自己的 *target* 目录。即使进入 *adder* 目录运行 `cargo build`,构建结果也位于 *add/target* 而不是 *add/adder/target*。工作空间中的 crate 之间相互依赖。如果每个 crate 有其自己的 *target* 目录,为了在自己的 *target* 目录中生成构建结果,工作空间中的每一个 crate 都不得不相互重新编译其他 crate。通过共享一个 *target* 目录,工作空间可以避免其他 crate 重复构建。
|
||||||
|
|
||||||
### 在工作空间中创建第二个包
|
### 在工作空间中创建第二个包
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 704c51eec2f26a0133ae17a2c01986590c05a045
|
> commit 704c51eec2f26a0133ae17a2c01986590c05a045
|
||||||
|
|
||||||
`cargo install` 命令用于在本地安装和使用二进制 crate。它并不打算替换系统中的包;它意在作为一个方便 Rust 开发者们安装其他人已经在 [crates.io](https://crates.io/)<!-- ignore --> 上共享的工具的手段。只有拥有二进制目标文件的包能够被安装。**二进制目标** 文件是在 crate 有 *src/main.rs* 或者其他指定为二进制文件时所创建的可执行程序,这不同于自身不能执行但适合包含在其他程序中的库目标文件。通常 crate 的 *README* 文件中有该 crate 是库、二进制目标还是两者都是的信息。
|
`cargo install` 命令用于在本地安装和使用二进制 crate。它并不打算替换系统中的包;它意在作为一个方便 Rust 开发者们安装其他人已经在 [crates.io](https://crates.io/)<!-- ignore --> 上共享的工具的手段。只有拥有二进制目标文件的包能够被安装。**二进制目标** 文件是在 crate 有 *src/main.rs* 或者其他指定为二进制文件时所创建的可执行程序,这不同于自身不能执行但适合包含在其他程序中的库目标文件。通常 crate 的 *README* 文件中有该 crate 是库、二进制目标还是两者兼有的信息。
|
||||||
|
|
||||||
所有来自 `cargo install` 的二进制文件都安装到 Rust 安装根目录的 *bin* 文件夹中。如果你使用 *rustup.rs* 安装的 Rust 且没有自定义任何配置,这将是 `$HOME/.cargo/bin`。确保将这个目录添加到 `$PATH` 环境变量中就能够运行通过 `cargo install` 安装的程序了。
|
所有来自 `cargo install` 的二进制文件都安装到 Rust 安装根目录的 *bin* 文件夹中。如果你是使用 *rustup.rs* 来安装 Rust 且没有自定义任何配置,这个目录将是 `$HOME/.cargo/bin`。确保将这个目录添加到 `$PATH` 环境变量中就能够运行通过 `cargo install` 安装的程序了。
|
||||||
|
|
||||||
例如,第十二章提到的叫做 `ripgrep` 的用于搜索文件的 `grep` 的 Rust 实现。为了安装 `ripgrep` 运行如下:
|
例如,第十二章提到的叫做 `ripgrep` 的用于搜索文件的 `grep` 的 Rust 实现。为了安装 `ripgrep` 运行如下:
|
||||||
|
|
||||||
|
@ -8,4 +8,4 @@ Cargo 的设计使得开发者可以通过新的子命令来对 Cargo 进行扩
|
|||||||
|
|
||||||
## 总结
|
## 总结
|
||||||
|
|
||||||
通过 Cargo 和 [crates.io](https://crates.io/)<!-- ignore --> 来分享代码是使得 Rust 生态环境可以用于许多不同的任务的重要组成部分。Rust 的标准库是小而稳定的,不过 crate 易于分享和使用,并采用一个不同语言自身的时间线来提供改进。不要羞于在 [crates.io](https://crates.io/)<!-- ignore --> 上共享对你有用的代码;因为它很有可能对别人也很有用!
|
通过 Cargo 和 [crates.io](https://crates.io/)<!-- ignore --> 来分享代码是使得 Rust 生态环境可以用于许多不同的任务的重要组成部分。Rust 的标准库是小而稳定的,不过 crate 易于分享和使用,并采用一个不同语言自身的时间线来提供改进。不要羞于在 [crates.io](https://crates.io/)<!-- ignore --> 上共享对你有用的代码,因为它很有可能对别人也很有用!
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
### Box 允许创建递归类型
|
### Box 允许创建递归类型
|
||||||
|
|
||||||
**递归类型**(_recursive type_)的值可以拥有另一个同类型的值作为其的一部分。这会产生一个问题因为 Rust 需要在编译时知道类型占用多少空间。递归类型的值嵌套理论上可以无限的进行下去,所以 Rust 不知道递归类型需要多少空间。因为 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。
|
**递归类型**(_recursive type_)的值可以拥有另一个同类型的值作为其自身的一部分。但是这会产生一个问题,因为 Rust 需要在编译时知道类型占用多少空间。递归类型的值嵌套理论上可以无限地进行下去,所以 Rust 不知道递归类型需要多少空间。因为 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。
|
||||||
|
|
||||||
作为一个递归类型的例子,让我们探索一下 _cons list_。这是一个函数式编程语言中常见的数据类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
|
作为一个递归类型的例子,让我们探索一下 _cons list_。这是一个函数式编程语言中常见的数据类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理因此也没有所有者。
|
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理因此也没有所有者。
|
||||||
|
|
||||||
为了启用多所有权需要显式地使用 Rust 类型 `Rc<T>`,其为 **引用计数**(_reference counting_)的缩写。引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表没有任何有效引用并可以被清理。
|
为了启用多所有权需要显式地使用 Rust 类型 `Rc<T>`,其为 **引用计数**(_reference counting_)的缩写。引用计数意味着记录一个值的引用数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表没有任何有效引用并可以被清理。
|
||||||
|
|
||||||
可以将其想象为客厅中的电视。当一个人进来看电视时,他打开电视。其他人也可以进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候就关掉了电视,正在看电视的人肯定会抓狂的!
|
可以将其想象为客厅中的电视。当一个人进来看电视时,他打开电视。其他人也可以进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候就关掉了电视,正在看电视的人肯定会抓狂的!
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
#### 内部可变性的用例:mock 对象
|
#### 内部可变性的用例:mock 对象
|
||||||
|
|
||||||
有时在测试中程序员会用某个类型替换另一个类型,以便观察特定的行为并断言它是被正确实现的。这个占位符类型被称为 **测试替身**(_test double_)。可以把它想象成电影制作中的 “替身演员”("stunt double"),这是一个龙套进入并替代演员进行一些特定的高难度操作。测试替身在运行测试时替代某个类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
|
有时在测试中程序员会用某个类型替换另一个类型,以便观察特定的行为并断言它是被正确实现的。这个占位符类型被称为 **测试替身**(_test double_)。就像电影制作中的替身演员(_stunt double_)一样,替代演员完成高难度的场景。测试替身在运行测试时替代某个类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
|
||||||
|
|
||||||
虽然 Rust 中的对象与其他语言中的对象并不是一回事,Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。
|
虽然 Rust 中的对象与其他语言中的对象并不是一回事,Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit c06006157b14b3d47b5c716fc392b77f3b2e21ce
|
> commit c06006157b14b3d47b5c716fc392b77f3b2e21ce
|
||||||
|
|
||||||
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄漏**(_memory leak_)),但并不是不可能。与在编译时拒绝数据竞争不同,Rust 并不保证完全地避免内存泄漏,这意味着内存泄漏在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0,其值也永远不会被丢弃。
|
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄漏**(_memory leak_)),但并不是不可能。Rust 并不保证完全防止内存泄漏,这意味着内存泄漏在 Rust 中被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0,持有的数据也就永远不会被释放。
|
||||||
|
|
||||||
### 制造引用循环
|
### 制造引用循环
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
|
|||||||
|
|
||||||
<span class="caption">示例 15-25: 一个存放 `RefCell` 的 cons list 定义,这样可以修改 `Cons` 成员所引用的数据</span>
|
<span class="caption">示例 15-25: 一个存放 `RefCell` 的 cons list 定义,这样可以修改 `Cons` 成员所引用的数据</span>
|
||||||
|
|
||||||
这里采用了示例 15-25 中 `List` 定义的另一种变体。现在 `Cons` 成员的第二个元素是 `RefCell<Rc<List>>`,这意味着不同于像示例 15-24 那样能够修改 `i32` 的值,我们希望能够修改 `Cons` 成员所指向的 `List`。这里还增加了一个 `tail` 方法来方便我们在有 `Cons` 成员的时候访问其第二项。
|
这里采用了示例 15-5 中 `List` 定义的另一种变体。现在 `Cons` 成员的第二个元素是 `RefCell<Rc<List>>`,这意味着不同于像示例 15-24 那样能够修改 `i32` 的值,我们希望能够修改 `Cons` 成员所指向的 `List`。这里还增加了一个 `tail` 方法来方便我们在有 `Cons` 成员的时候访问其第二项。
|
||||||
|
|
||||||
在示例 15-26 中增加了一个 `main` 函数,其使用了示例 15-25 中的定义。这些代码在 `a` 中创建了一个列表,一个指向 `a` 中列表的 `b` 列表,接着修改 `a` 中的列表指向 `b` 中的列表,这会创建一个引用循环。在这个过程的多个位置有 `println!` 语句展示引用计数。
|
在示例 15-26 中增加了一个 `main` 函数,其使用了示例 15-25 中的定义。这些代码在 `a` 中创建了一个列表,一个指向 `a` 中列表的 `b` 列表,接着修改 `a` 中的列表指向 `b` 中的列表,这会创建一个引用循环。在这个过程的多个位置有 `println!` 语句展示引用计数。
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
|
|||||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-27/src/main.rs:here}}
|
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-27/src/main.rs:here}}
|
||||||
```
|
```
|
||||||
|
|
||||||
我们希望能够 `Node` 拥有其子节点,同时也希望能将所有权共享给变量,以便可以直接访问树中的每一个 `Node`,为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>`。我们还希望能修改其他节点的子节点,所以 `children` 中 `Vec<Rc<Node>>` 被放进了 `RefCell<T>`。
|
我们希望 `Node` 能够拥有其子节点,同时也希望能将所有权共享给变量,以便可以直接访问树中的每一个 `Node`,为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>`。我们还希望能修改其他节点的子节点,所以 `children` 中 `Vec<Rc<Node>>` 被放进了 `RefCell<T>`。
|
||||||
|
|
||||||
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 3 且没有子节点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子节点的实例 `branch`,如示例 15-27 所示:
|
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 3 且没有子节点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子节点的实例 `branch`,如示例 15-27 所示:
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
|
|||||||
|
|
||||||
<span class="caption">示例 15-27:创建没有子节点的 `leaf` 节点和以 `leaf` 作为子节点的 `branch` 节点</span>
|
<span class="caption">示例 15-27:创建没有子节点的 `leaf` 节点和以 `leaf` 作为子节点的 `branch` 节点</span>
|
||||||
|
|
||||||
这里克隆了 `leaf` 中的 `Rc<Node>` 并储存在了 `branch` 中,这意味着 `leaf` 中的 `Node` 现在有两个所有者:`leaf`和`branch`。可以通过 `branch.children` 从 `branch` 中获得 `leaf`,不过无法从 `leaf` 到 `branch`。`leaf` 没有到 `branch` 的引用且并不知道它们相互关联。我们希望 `leaf` 知道 `branch` 是其父节点。稍后我们会这么做。
|
这里克隆了 `leaf` 中的 `Rc<Node>` 并储存在 `branch` 中,这意味着 `leaf` 中的 `Node` 现在有两个所有者:`leaf`和`branch`。可以通过 `branch.children` 从 `branch` 中获得 `leaf`,不过无法从 `leaf` 到 `branch`。`leaf` 没有到 `branch` 的引用且并不知道它们相互关联。我们希望 `leaf` 知道 `branch` 是其父节点。稍后我们会这么做。
|
||||||
|
|
||||||
#### 增加从子到父的引用
|
#### 增加从子到父的引用
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||||
|
|
||||||
安全且高效的处理并发编程是 Rust 的另一个主要目标。**并发编程**(_Concurrent programming_),代表程序的不同部分相互独立的执行,而 **并行编程**(_parallel programming_)代表程序不同部分于同时执行,这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因,在此类上下文中编程一直是困难且容易出错的:Rust 希望能改变这一点。
|
安全且高效地处理并发编程是 Rust 的另一个主要目标。**并发编程**(_Concurrent programming_),代表程序的不同部分相互独立地执行,而 **并行编程**(_parallel programming_)代表程序不同部分同时执行,这两个概念随着计算机越来越多的利用多处理器的优势而显得愈发重要。由于历史原因,在此类上下文中编程一直是困难且容易出错的:Rust 希望能改变这一点。
|
||||||
|
|
||||||
起初,Rust 团队认为确保内存安全和防止并发问题是两个分别需要不同方法应对的挑战。随着时间的推移,团队发现所有权和类型系统是一系列解决内存安全 **和** 并发问题的强有力的工具!通过利用所有权和类型检查,在 Rust 中很多并发错误都是 **编译时** 错误,而非运行时错误。因此,相比花费大量时间尝试重现运行时并发 bug 出现的特定情况,Rust 会拒绝编译不正确的代码并提供解释问题的错误信息。因此,你可以在开发时修复代码,而不是在部署到生产环境后修复代码。我们给 Rust 的这一部分起了一个绰号 **无畏并发**(_fearless concurrency_)。无畏并发令你的代码免于出现诡异的 bug 并可以轻松重构且无需担心会引入新的 bug。
|
起初,Rust 团队认为确保内存安全和防止并发问题是两个分别需要不同方法应对的挑战。随着时间的推移,团队发现所有权和类型系统是一系列解决内存安全 **和** 并发问题的强有力的工具!通过利用所有权和类型检查,在 Rust 中很多并发错误都是 **编译时** 错误,而非运行时错误。因此,相比花费大量时间尝试重现运行时并发 bug 出现的特定情况,Rust 会拒绝编译不正确的代码并提供解释问题的错误信息。因此,你可以在开发时修复代码,而不是在部署到生产环境后修复代码。我们给 Rust 的这一部分起了一个绰号 **无畏并发**(_fearless concurrency_)。无畏并发令你的代码免于出现诡异的 bug 并可以轻松重构且无需担心会引入新的 bug。
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 8aecae3efe5ca8f79f055b70f05d9a3f990bce7b
|
> commit 8aecae3efe5ca8f79f055b70f05d9a3f990bce7b
|
||||||
|
|
||||||
在大部分现代操作系统中,已执行程序的代码在一个 **进程**(_process_)中运行,操作系统则会负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这些运行这些独立部分的功能被称为 **线程**(_threads_)。例如,web 服务器可以有多个线程以便可以同时响应多于一个请求。
|
在大部分现代操作系统中,已执行程序的代码在一个 **进程**(_process_)中运行,操作系统则会负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这些运行这些独立部分的功能被称为 **线程**(_threads_)。例如,web 服务器可以有多个线程以便可以同时响应多个请求。
|
||||||
|
|
||||||
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
|
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **
|
|||||||
|
|
||||||
### 手动实现 `Send` 和 `Sync` 是不安全的
|
### 手动实现 `Send` 和 `Sync` 是不安全的
|
||||||
|
|
||||||
通常并不需要手动实现 `Send` 和 `Sync` trait,因为由 `Send` 和 `Sync` 的类型组成的类型,自动就是 `Send` 和 `Sync` 的。因为它们是标记 trait,甚至都不需要实现任何方法。们只是用来加强并发相关的不可变性的。
|
通常并不需要手动实现 `Send` 和 `Sync` trait,因为由 `Send` 和 `Sync` 的类型组成的类型,自动就是 `Send` 和 `Sync` 的。因为它们是标记 trait,甚至都不需要实现任何方法。它们只是用来加强并发相关的不可变性的。
|
||||||
|
|
||||||
手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,第十九章将会讲述具体的方法;当前重要的是,在创建新的由不是 `Send` 和 `Sync` 的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[“The Rustonomicon”][nomicon] 中有更多关于这些保证以及如何维持它们的信息。
|
手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,第十九章将会讲述具体的方法;当前重要的是,在创建新的由不是 `Send` 和 `Sync` 的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[“The Rustonomicon”][nomicon] 中有更多关于这些保证以及如何维持它们的信息。
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user