Skip to content

Commit

Permalink
update to ch17-02 close #733
Browse files Browse the repository at this point in the history
  • Loading branch information
KaiserY committed Sep 18, 2023
1 parent d2cac68 commit f377834
Show file tree
Hide file tree
Showing 6 changed files with 9 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/ch15-05-interior-mutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@

<span class="caption">示例 15-20:一个记录某个值与最大值差距的库,并根据此值的特定级别发出警告</span>

这些代码中一个重要部分是拥有一个方法 `send``Messenger` trait,其获取一个 `self` 的不可变引用和文本信息。这个 trait 是 mock 对象所需要实现的接口库,这样 mock 就能像一个真正的对象那样使用了。另一个重要的部分是我们需要测试 `LimitTracker``set_value` 方法的行为。可以改变传递的 `value` 参数的值,不过 `set_value` 并没有返回任何可供断言的值。也就是说,如果使用某个实现了 `Messenger` trait 的值和特定的 `max` 创建 `LimitTracker`,当传递不同 `value` 值时,消息发送者应被告知发送合适的消息。
这些代码中一个重要部分是拥有一个方法 `send``Messenger` trait,其获取一个 `self` 的不可变引用和文本信息。这个 trait 是 mock 对象所需要实现的接口库,这样 mock 就能像一个真正的对象那样使用了。另一个重要的部分是我们需要测试 `LimitTracker``set_value` 方法的行为。可以改变传递的 `value` 参数的值,不过 `set_value` 并没有返回任何可供断言的值。我们希望能够说,如果我们创建一个实现了 `Messenger` trait 和具有特定 `max` 值的 `LimitTracker`,当传递不同 `value` 值时,消息发送者应被告知发送合适的消息。

我们所需的 mock 对象是,调用 `send` 并不实际发送 email 或消息,而是只记录信息被通知要发送了。可以新建一个 mock 对象实例,用其创建 `LimitTracker`,调用 `LimitTracker``set_value` 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许:

Expand Down
4 changes: 2 additions & 2 deletions src/ch15-06-reference-cycles.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理

### 避免引用循环:将 `Rc<T>` 变为 `Weak<T>`

到目前为止,我们已经展示了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count`,和只在其 `strong_count` 为 0 时才会被清理的 `Rc<T>` 实例。你也可以通过调用 `Rc::downgrade` 并传递 `Rc<T>` 实例的引用来创建其值的 **弱引用**_weak reference_)。强引用代表如何共享 `Rc<T>` 实例的所有权。弱引用并不属于所有权关系,当 `Rc<T>` 实例被清理时其计数没有影响。他们不会造成引用循环,因为任何弱引用的循环会在其相关的强引用计数为 0 时被打断。
到目前为止,我们已经展示了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count`,和只在其 `strong_count` 为 0 时才会被清理的 `Rc<T>` 实例。你也可以通过调用 `Rc::downgrade` 并传递 `Rc<T>` 实例的引用来创建其值的 **弱引用**_weak reference_)。强引用代表如何共享 `Rc<T>` 实例的所有权。弱引用并不属于所有权关系,当 `Rc<T>` 实例被清理时其计数没有影响。他们不会造成引用循环,因为任何涉及弱引用的循环会在其相关的值的强引用计数为 0 时被打断。

调用 `Rc::downgrade` 时会得到 `Weak<T>` 类型的智能指针。不同于将 `Rc<T>` 实例的 `strong_count` 加 1,调用 `Rc::downgrade` 会将 `weak_count` 加 1。`Rc<T>` 类型使用 `weak_count` 来记录其存在多少个 `Weak<T>` 引用,类似于 `strong_count`。其区别在于 `weak_count` 无需计数为 0 就能使 `Rc<T>` 实例被清理。

Expand All @@ -76,7 +76,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
{{#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 所示:

Expand Down
4 changes: 2 additions & 2 deletions src/ch16-01-threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ hi number 4 from the main thread!

诸如将 `join` 放置于何处这样的小细节,会影响线程是否同时运行。

### 线程与 `move` 闭包
### `move` 闭包与线程一同使用

`move` 关键字经常用于传递给 `thread::spawn` 的闭包,因为闭包会获取从环境中取得的值的所有权,因此会将这些值的所有权从一个线程传送到另一个线程。在第十三章 [“闭包会捕获其环境”][capture] 部分讨论了闭包上下文中的 `move`。现在我们会更专注于 `move``thread::spawn` 之间的交互。

Expand All @@ -130,7 +130,7 @@ hi number 4 from the main thread!
{{#include ../listings/ch16-fearless-concurrency/listing-16-03/output.txt}}
```

Rust 会 **推断** 如何捕获 `v`,因为 `println!` 只需要 `v` 的引用,闭包尝试借用 `v`。然而这有一个问题:Rust 不知道这个新建线程会执行多久,所以无法知晓 `v` 的引用是否一直有效。
Rust 会 **推断** 如何捕获 `v`,因为 `println!` 只需要 `v` 的引用,闭包尝试借用 `v`。然而这有一个问题:Rust 不知道这个新建线程会执行多久,所以无法知晓对 `v` 的引用是否一直有效。

示例 16-4 展示了一个 `v` 的引用很有可能不再有效的场景:

Expand Down
4 changes: 1 addition & 3 deletions src/ch17-00-oop.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
> <br>
> commit 398d6f48d2e6b7b15efd51c4541d446e89de3892
面向对象编程(Object-Oriented Programming,OOP)是一种模式化编程方式。对象(Object)作为一个编程概念来源于 20 世纪 60 年代的 Simula 编程语言。这些对象影响了 Alan Kay 的编程架构中对象之间的消息传递。

对象(Object)来源于 20 世纪 60 年代的 Simula 编程语言。为了描述这种架构,他在 1967 年创造了 **面向对象编程***object-oriented programming*)这个术语。关于 OOP 是什么有很多相互矛盾的定义;在一些定义下,Rust 是面向对象的;在其他定义下,Rust 不是。在本章节中,我们会探索一些被普遍认为是面向对象的特性和这些特性是如何体现在 Rust 语言习惯中的。接着会展示如何在 Rust 中实现面向对象设计模式,并讨论这么做与利用 Rust 自身的一些优势实现的方案相比有什么取舍。
面向对象编程(Object-Oriented Programming,OOP)是一种对程序进行建模方式。对象(Object)作为一个编程概念来源于 20 世纪 60 年代的 Simula 编程语言。这些对象影响了 Alan Kay 的编程架构,该架构中对象之间互相传递消息。他在 1967 年创造了 **面向对象编程***object-oriented programming*)这个术语。关于 OOP 是什么有很多相互矛盾的定义;在一些定义下,Rust 是面向对象的;在其他定义下,Rust 不是。在本章节中,我们会探索一些被普遍认为是面向对象的特性和这些特性是如何体现在 Rust 语言习惯中的。接着会展示如何在 Rust 中实现面向对象设计模式,并讨论这么做与利用 Rust 自身的一些优势实现的方案相比有什么取舍。
2 changes: 1 addition & 1 deletion src/ch17-01-what-is-oo.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@

另外某些语言还只允许单继承(意味着子类只能继承一个父类),进一步限制了程序设计的灵活性。

因为这些原因,Rust 选择了一个不同的途径,使用 trait 对象而不是继承。让我们看一下 Rust 中的 trait 对象是如何实现多态的
因为这些原因,Rust 选择了一个不同的途径,使用 trait 对象而不是继承。让我们看一下 trait 对象如何使 Rust 得以实现多态的
4 changes: 2 additions & 2 deletions src/ch17-02-trait-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@

<span class="caption">示例 17-8: 另一个使用 `gui` 的 crate 中,在 `SelectBox` 结构体上实现 `Draw` trait</span>

库使用者现在可以在他们的 `main` 函数中创建一个 `Screen` 实例。至此可以通过将 `SelectBox``Button` 放入 `Box<T>` 转变为 trait 对象来增加组件。接着可以调用 `Screen``run` 方法,它会调用每个组件的 `draw` 方法。示例 17-9 展示了这个实现:
库使用者现在可以在他们的 `main` 函数中创建一个 `Screen` 实例。至此可以通过将 `SelectBox``Button` 放入 `Box<T>` 转变为 trait 对象再放入 `Screen` 实例中。接着可以调用 `Screen``run` 方法,它会调用每个组件的 `draw` 方法。示例 17-9 展示了这个实现:

<span class="filename">文件名:src/main.rs</span>

Expand Down Expand Up @@ -122,7 +122,7 @@

### trait 对象执行动态分发

回忆一下第十章 [“泛型代码的性能”][performance-of-code-using-generics] 部分讨论过的,当对泛型使用 trait bound 时编译器所执行的单态化处理:编译器为每一个被泛型类型参数代替的具体类型生成了函数和方法的非泛型实现。单态化产生的代码在执行 **静态分发***static dispatch*)。静态分发发生于编译器在编译时就知晓调用了什么方法的时候。这与 **动态分发***dynamic dispatch*)相对,这时编译器在编译时无法知晓调用了什么方法。在动态分发的场景下,编译器生成的代码到运行时才能确定调用了什么方法
回忆一下第十章 [“泛型代码的性能”][performance-of-code-using-generics] 部分讨论过的,当对泛型使用 trait bound 时编译器所执行的单态化处理:编译器为每一个被泛型类型参数代替的具体类型生成了函数和方法的非泛型实现。单态化产生的代码在执行 **静态分发***static dispatch*)。静态分发发生于编译器在编译时就知晓调用了什么方法的时候。这与 **动态分发***dynamic dispatch*)相对,这时编译器在编译时无法知晓调用了什么方法。在动态分发的场景下,编译器会生成负责在运行时确定该调用什么方法的代码

当使用 trait 对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于 trait 对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用 trait 对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。尽管在编写示例 17-5 和可以支持示例 17-9 中的代码的过程中确实获得了额外的灵活性,但仍然需要权衡取舍。

Expand Down

0 comments on commit f377834

Please sign in to comment.