Merge pull request #438 from Nangos/patch-30

Update ch15-05-interior-mutability.md
This commit is contained in:
KaiserY 2020-07-12 22:20:03 +08:00 committed by GitHub
commit 0c097ef823
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -11,7 +11,7 @@
不同于 `Rc<T>``RefCell<T>` 代表其数据的唯一的所有权。那么是什么让 `RefCell<T>` 不同于像 `Box<T>` 这样的类型呢?回忆一下第四章所学的借用规则: 不同于 `Rc<T>``RefCell<T>` 代表其数据的唯一的所有权。那么是什么让 `RefCell<T>` 不同于像 `Box<T>` 这样的类型呢?回忆一下第四章所学的借用规则:
1. 在任意给定时间,只能拥有一个可变引用或任意数量的不可变引用 **之一**(而不是全部)。 1. 在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用 **之一**(而不是两者)。
2. 引用必须总是有效的。 2. 引用必须总是有效的。
对于引用和 `Box<T>`,借用规则的不可变性作用于编译时。对于 `RefCell<T>`,这些不可变性作用于 **运行时**。对于引用,如果违反这些规则,会得到一个编译错误。而对于 `RefCell<T>`,如果违反这些规则程序会 panic 并退出。 对于引用和 `Box<T>`,借用规则的不可变性作用于编译时。对于 `RefCell<T>`,这些不可变性作用于 **运行时**。对于引用,如果违反这些规则,会得到一个编译错误。而对于 `RefCell<T>`,如果违反这些规则程序会 panic 并退出。
@ -55,7 +55,7 @@ error[E0596]: cannot borrow immutable local variable `x` as mutable
| ^ cannot borrow mutably | ^ cannot borrow mutably
``` ```
然而,特定情况下在值的方法内部能够修改自身是很有用的,而不是在其他代码中。此时值仍然是不可变的,值方法外部的代码不能修改其值。`RefCell<T>` 是一个获得内部可变性的方法。`RefCell<T>` 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应地在运行时检查借用规则。如果违反了这些规则,会出现 panic 而不是编译错误。 然而,特定情况下,令一个值在其方法内部能够修改自身,而在其他代码中仍视为不可变,是很有用的。值方法外部的代码就不能修改其值了。`RefCell<T>` 是一个获得内部可变性的方法。`RefCell<T>` 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应地在运行时检查借用规则。如果违反了这些规则,会出现 panic 而不是编译错误。
让我们通过一个实际的例子来探索何处可以使用 `RefCell<T>` 来修改不可变值并看看为何这么做是有意义的。 让我们通过一个实际的例子来探索何处可以使用 `RefCell<T>` 来修改不可变值并看看为何这么做是有意义的。
@ -63,7 +63,7 @@ error[E0596]: cannot borrow immutable local variable `x` as mutable
**测试替身**_test double_是一个通用编程概念它代表一个在测试中替代某个类型的类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。 **测试替身**_test double_是一个通用编程概念它代表一个在测试中替代某个类型的类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
虽然 Rust 没有与其他语言中的对象完全相同的对象Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。 虽然 Rust 中的对象与其他语言中的对象并不是一回事Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。
如下是一个我们想要测试的场景:我们在编写一个记录某个值与最大值的差距的库,并根据当前值与最大值的差距来发送消息。例如,这个库可以用于记录用户所允许的 API 调用数量限额。 如下是一个我们想要测试的场景:我们在编写一个记录某个值与最大值的差距的库,并根据当前值与最大值的差距来发送消息。例如,这个库可以用于记录用户所允许的 API 调用数量限额。
@ -112,7 +112,7 @@ impl<'a, T> LimitTracker<'a, T>
这些代码中一个重要部分是拥有一个方法 `send``Messenger` trait其获取一个 `self` 的不可变引用和文本信息。这是我们的 mock 对象所需要拥有的接口。另一个重要的部分是我们需要测试 `LimitTracker``set_value` 方法的行为。可以改变传递的 `value` 参数的值,不过 `set_value` 并没有返回任何可供断言的值。也就是说,如果使用某个实现了 `Messenger` trait 的值和特定的 `max` 创建 `LimitTracker`,当传递不同 `value` 值时,消息发送者应被告知发送合适的消息。 这些代码中一个重要部分是拥有一个方法 `send``Messenger` trait其获取一个 `self` 的不可变引用和文本信息。这是我们的 mock 对象所需要拥有的接口。另一个重要的部分是我们需要测试 `LimitTracker``set_value` 方法的行为。可以改变传递的 `value` 参数的值,不过 `set_value` 并没有返回任何可供断言的值。也就是说,如果使用某个实现了 `Messenger` trait 的值和特定的 `max` 创建 `LimitTracker`,当传递不同 `value` 值时,消息发送者应被告知发送合适的消息。
我们所需的 mock 对象是,调用 `send` 不同于实际发送 email 或短息,其只记录信息被通知要发送了。可以新建一个 mock 对象示例,用其创建 `LimitTracker`,调用 `LimitTracker``set_value` 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许: 我们所需的 mock 对象是,调用 `send` 并不实际发送 email 或消息,而是只记录信息被通知要发送了。可以新建一个 mock 对象示例,用其创建 `LimitTracker`,调用 `LimitTracker``set_value` 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许:
<span class="filename">文件名: src/lib.rs</span> <span class="filename">文件名: src/lib.rs</span>
@ -343,6 +343,6 @@ c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
这是非常巧妙的!通过使用 `RefCell<T>`,我们可以拥有一个表面上不可变的 `List`,不过可以使用 `RefCell<T>` 中提供内部可变性的方法来在需要时修改数据。`RefCell<T>` 的运行时借用规则检查也确实保护我们免于出现数据竞争——有时为了数据结构的灵活性而付出一些性能是值得的。 这是非常巧妙的!通过使用 `RefCell<T>`,我们可以拥有一个表面上不可变的 `List`,不过可以使用 `RefCell<T>` 中提供内部可变性的方法来在需要时修改数据。`RefCell<T>` 的运行时借用规则检查也确实保护我们免于出现数据竞争——有时为了数据结构的灵活性而付出一些性能是值得的。
标准库中也有其他提供内部可变性的类型,比如 `Cell<T>`,它有些类似 `RefCell<T>`,除了提供内部值的引用,其值还会被拷贝进和拷贝出 `Cell<T>`。还有 `Mutex<T>`,其提供线程间安全的内部可变性,我们将在第 16 章中讨论其用法。请查看标准库来获取更多细节不同类型之间的区别。 标准库中也有其他提供内部可变性的类型,比如 `Cell<T>`,它类似 `RefCell<T>` 但有一点除外:它并非提供内部值的引用,而是把值拷贝进和拷贝出 `Cell<T>`。还有 `Mutex<T>`,其提供线程间安全的内部可变性,我们将在第 16 章中讨论其用法。请查看标准库来获取更多细节关于这些不同类型之间的区别。
[wheres-the---operator]: ch05-03-method-syntax.html#wheres-the---operator [wheres-the---operator]: ch05-03-method-syntax.html#wheres-the---operator