Merge pull request #418 from wtklbm/master

更新智能指针部分内容
This commit is contained in:
KaiserY 2020-06-26 13:01:24 +08:00 committed by GitHub
commit ccd2c0ae0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 6 additions and 6 deletions

View File

@ -20,6 +20,6 @@
* `Rc<T>`,一个引用计数类型,其数据可以有多个所有者
* `Ref<T>``RefMut<T>`,通过 `RefCell<T>` 访问,一个在运行时而不是在编译时执行借用规则的类型。
另外我们会涉及 **内部可变性***interior mutability*)模式,这是不可变类型暴露出改变其内部值的 API。我们也会讨论 **引用循环***reference cycles*)会如何泄内存,以及如何避免。
另外我们会涉及 **内部可变性***interior mutability*)模式,这是不可变类型暴露出改变其内部值的 API。我们也会讨论 **引用循环***reference cycles*)会如何泄内存,以及如何避免。
让我们开始吧!

View File

@ -55,7 +55,7 @@ error[E0596]: cannot borrow immutable local variable `x` as mutable
| ^ cannot borrow mutably
```
然而,特定情况下在值的方法内部能够修改自身是很有用的,而不是在其他代码中。此时值仍然是不可变的,值方法外部的代码不能修改其值。`RefCell<T>` 是一个获得内部可变性的方法。`RefCell<T>` 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应地在运行时检查借用规则。如果违反了这些规则,会得到 `panic!` 而不是编译错误。
然而,特定情况下在值的方法内部能够修改自身是很有用的,而不是在其他代码中。此时值仍然是不可变的,值方法外部的代码不能修改其值。`RefCell<T>` 是一个获得内部可变性的方法。`RefCell<T>` 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应地在运行时检查借用规则。如果违反了这些规则,会出现 panic 而不是编译错误。
让我们通过一个实际的例子来探索何处可以使用 `RefCell<T>` 来修改不可变值并看看为何这么做是有意义的。
@ -255,9 +255,9 @@ mod tests {
### `RefCell<T>` 在运行时记录借用
当创建不可变和可变引用时,我们分别使用 `&``&mut` 语法。对于 `RefCell<T>` 来说,则是 `borrow``borrow_mut` 方法,这属于 `RefCell<T>` 安全 API 的一部分。`borrow` 方法返回 `Ref` 类型的智能指针,`borrow_mut` 方法返回 `RefMut` 类型的智能指针。这两个类型都实现了 `Deref`,所以可以当作常规引用对待。
当创建不可变和可变引用时,我们分别使用 `&``&mut` 语法。对于 `RefCell<T>` 来说,则是 `borrow``borrow_mut` 方法,这属于 `RefCell<T>` 安全 API 的一部分。`borrow` 方法返回 `Ref<T>` 类型的智能指针,`borrow_mut` 方法返回 `RefMut` 类型的智能指针。这两个类型都实现了 `Deref`,所以可以当作常规引用对待。
`RefCell<T>` 记录当前有多少个活动的 `Ref<T>``RefMut<T>` 智能指针。每次调用 `borrow``RefCell<T>` 将活动的不可变借用计数加一。当 `Ref` 值离开作用域时,不可变借用计数减一。就像编译时借用规则一样,`RefCell<T>` 在任何时候只允许有多个不可变借用或一个可变借用。
`RefCell<T>` 记录当前有多少个活动的 `Ref<T>``RefMut<T>` 智能指针。每次调用 `borrow``RefCell<T>` 将活动的不可变借用计数加一。当 `Ref<T>` 值离开作用域时,不可变借用计数减一。就像编译时借用规则一样,`RefCell<T>` 在任何时候只允许有多个不可变借用或一个可变借用。
如果我们尝试违反这些规则,相比引用时的编译时错误,`RefCell<T>` 的实现会在运行时出现 panic。示例 15-23 展示了对示例 15-22 中 `send` 实现的修改,这里我们故意尝试在相同作用域创建两个可变借用以便演示 `RefCell<T>` 不允许我们在运行时这么做:

View File

@ -3,7 +3,7 @@
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/master/src/ch15-06-reference-cycles.md) > <br>
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄**_memory leak_但并不是不可能。与在编译时拒绝数据竞争不同 Rust 并不保证完全地避免内存泄露,这意味着内存泄露在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>``RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄,因为每一项的引用计数永远也到不了 0其值也永远不会被丢弃。
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄**_memory leak_但并不是不可能。与在编译时拒绝数据竞争不同 Rust 并不保证完全地避免内存泄漏,这意味着内存泄漏在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>``RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄,因为每一项的引用计数永远也到不了 0其值也永远不会被丢弃。
### 制造引用循环

View File

@ -219,6 +219,6 @@ Result: 10
你可能注意到了,因为 `counter` 是不可变的,不过可以获取其内部值的可变引用;这意味着 `Mutex<T>` 提供了内部可变性,就像 `Cell` 系列类型那样。正如第十五章中使用 `RefCell<T>` 可以改变 `Rc<T>` 中的内容那样,同样的可以使用 `Mutex<T>` 来改变 `Arc<T>` 中的内容。
另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄。同理,`Mutex<T>` 也有造成 **死锁**_deadlock_ 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。
另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄。同理,`Mutex<T>` 也有造成 **死锁**_deadlock_ 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。
接下来,为了丰富本章的内容,让我们讨论一下 `Send``Sync` trait 以及如何对自定义类型使用他们。