需要全面的解释关于Rust中的cell和引用计数类型

98

在 Rust 标准库中有几种包装类型:

据我所知,这些都是提供了比简单引用更多可能性的包装器(wrapper)。虽然我了解一些基础知识,但我看不到整个图片。

它们确切地做什么?单元格(cell)和计数引用家族是否提供相似或正交的功能?


5
请描述您不理解的内容。您链接到的文档已经被数百(或数千)人阅读过,可能有数十到数百人做出了贡献。如果您不告诉我们您不理解的内容,那么我们能说些什么与已经表达的不同呢? - Shepmaster
1
@Shepmaster 文档中的解释涉及“内部可变性”。这个概念对我来说不太清楚。也许我的问题也不太清楚。 - Boiethios
3
一个快速的互联网搜索找到了有关内部可变性的书中一章,一篇博客文章,以及一个stackoverflow问题。或许现在你已经知道你不清楚的内容,可以进行一些研究并更新你的问题。 - Shepmaster
最新版本书籍的更新链接:https://doc.rust-lang.org/book/ch15-05-interior-mutability.html - Tim Keating
2个回答

182
在 Rust 中有两个重要的概念:
- 所有权(Ownership) - 可变性(Mutability)
各种指针类型(Box、Rc、Arc)与所有权相关:它们允许控制单个对象是否有一个或多个所有者。
另一方面,各种 cell(Cell、RefCell、Mutex、RwLock、AtomicXXX)与可变性有关。
Rust 安全性的基本规则是“别名不能与可变性同时存在”(Aliasing NAND Mutability)。也就是说,如果某个对象内部存在未完成的引用,则该对象只能在没有引用其内部的情况下进行安全地修改。
通常,编译时的借用检查器(borrow checker)会强制执行这个规则:
- 如果你有一个 &T,则不能在同一作用域内还有一个 &mut T, - 如果你有一个 &mut T,则不能在同一作用域内还有任何对该对象的引用。
然而,有时这并不足够灵活。有时确实需要(或想要)具有对同一对象的多个引用,并且对其进行修改。这时就可以使用 cell。
Cell 和 RefCell 的思路是在存在别名的情况下以受控的方式允许可变性:
- Cell 防止形成对其内部的引用,避免出现悬空引用。 - RefCell 将“别名与可变性互斥”的执行从编译时移至运行时。
这种功能有时被描述为提供“内部可变性”,即一个从外部看起来不可变的对象(&T),实际上是可以修改的。当此可变性跨越多个线程时,需要使用 Mutex、RwLock 或 AtomicXXX;它们提供相同的功能:
- AtomicXXX 就是 Cell:没有对内部的引用,只是进出。 - RwLock 就是 RefCell:可以通过 guards 获得对内部的引用。 - Mutex 是 RwLock 的简化版本,没有区分只读 guard 和写 guard;在概念上类似于仅具有 borrow_mut 方法的 RefCell。
如果你来自 C++ 背景,则需要理解这些概念的异同。
  • Boxunique_ptr 的替代品,
  • Arcshared_ptr 的替代品,
  • Rcshared_ptr 的非线程安全版本。

Cell 提供了类似于 mutable 的功能,但是还有额外的保证以避免别名问题。可以将 Cell 看作是 std::atomic,而 RefCell 则是 std::shared_mutex 的非线程安全版本(如果锁已被占用,则会抛出异常而不是阻塞)。


2
非常感谢您,您的回答完全解决了我对 Rust 中“复杂”类型的困惑。与 C++ 的比较也非常恰当。 - Boiethios
3
是的,这就是那个意思。每个引用/指针都是真实数据的“别名”(毕竟,一个人的不同名称就是别名),在软件中我们提到有多个别名同时存在时就称为别名。 - Matthieu M.
3
这本书的第一版有一个章节讨论了这个问题。在后来的重写中,它被删除了,我认为这很遗憾——也许可以把它作为附录或其他什么形式再加回去。 - trent
7
如果“别名异或可变性”是安全的规则,那就意味着“(没有别名)且(没有可变性)”是不安全的吗?但是,如果没有别名和可变性,那肯定是完全安全的。我认为你想要的并不是异或运算。 - Lii
13
也许口号应该使用 NAND 而不是 XOR :P - Rufflewind
显示剩余4条评论

49

感谢Matthieu的好回答,这里有一张图可以帮助人们找到他们需要的包装器:

+-----------+
| Ownership |
+--+--------+                              +================+
   |                         +-Static----->| T              |(1)
   |                         |             +================+
   |                         |
   |                         |             +================+
   |          +-----------+  | Local    Val| Cell<T>        |(1)
   +-Unique-->| Borrowing +--+-Dynamic---->|----------------|
   |          +-----------+  |          Ref| RefCell<T>     |(1)
   |                         |             +================+
   |                         |
   |                         |             +================+
   |                         | Threaded    | AtomicT        |(2)
   |                         +-Dynamic---->|----------------|
   |                                       | Mutex<T>       |(1)
   |                                       | RwLock<T>      |(1)
   |                                       +================+
   |
   |
   |                                       +================+
   |                         +-No--------->| Rc<T>          |
   |                         |             +================+
   | Locally  +-----------+  |
   +-Shared-->| Mutable?  +--+             +================+
   |          +-----------+  |          Val| Rc<Cell<T>>    |
   |                         +-Yes-------->|----------------|
   |                                    Ref| Rc<RefCell<T>> |
   |                                       +================+
   |
   |
   |                                       +================+
   |                         +-No--------->| Arc<T>         |
   |                         |             +================+
   | Shared   +-----------+  |
   +-Between->| Mutable?  +--+             +================+
     Threads  +-----------+  |             | Arc<AtomicT>   |(2)
                             +-Yes-------->|----------------|
                                           | Arc<Mutex<T>>  |
                                           | Arc<RwLock<T>> |
                                           +================+
  1. 在这些情况下,T 可以被替换为 Box<T>
  2. T 是一个 bool 或数字时,请使用 AtomicT

要知道何时应该使用 Mutex 还是 RwLock,请参见这个相关问题


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接