Cell 或 RefCell 是最佳选择的情况

Situations where Cell or RefCell is the best choice

什么时候需要使用 Cell or RefCell?似乎有许多其他类型选择可以代替这些,并且文档警告说使用 RefCell 有点像 "last resort".

使用这些类型是“code smell”吗?谁能举例说明使用这些类型比使用其他类型更有意义,例如 Rc 甚至 Box?

不,CellRefCell 不是 "code smells"。通常,可变性是 inherited,也就是说,当且仅当您对整个数据结构具有独占访问权时,您才可以改变一个字段或数据结构的一部分,因此您可以选择使用 mut 进入该级别的可变性(即,foo.x 继承 它的可变性或缺乏 foo )。这是一个非常强大的模式,只要它运行良好(出人意料的频繁),就应该使用它。但它对于所有代码的表现力都不够。

BoxRc 与此无关。与几乎所有其他类型一样,它们尊重继承的可变性:如果您对 Box 具有独占、可变访问权限,则可以改变 Box 的内容(因为这意味着您也具有对内容的独占访问权限).相反,你永远无法获得 &mutRc 的内容,因为 Rc 本质上是共享的(即可以有多个 Rc 引用相同的数据).

CellRefCell 的一个常见情况是您需要在多个位置之间共享可变数据。通常不允许对同一数据有两个 &mut 引用(并且有充分的理由!)。但是,有时您 需要 它,并且单元格类型可以安全地执行此操作。

这可以通过 Rc<RefCell<T>> 的通用组合来完成,只要有人使用它,数据就会一直存在,并且允许每个人(但一次只能有一个人!)改变它。或者它可以像 &Cell<i32> 一样简单(即使单元格包装在更有意义的类型中)。后者也常用于内部、私有、可变状态,如引用计数。

该文档实际上有几个示例,说明您将在哪里使用 CellRefCell。一个很好的例子实际上是 Rc 本身。创建新的 Rc 时,必须增加引用计数,但引用计数在所有 Rc 之间共享,因此,由于继承的可变性,这不可能起作用。 Rc 实际上 使用 Cell.

一个好的指导方针是尝试编写尽可能多的没有单元格类型的代码,但是当没有它们会造成太多伤害时使用它们。在某些情况下,没有细胞也有很好的解决方案,并且随着经验的积累,您将能够在以前错过它们时找到它们,但总会有一些事情没有它们是不可能的。

假设您想要或需要创建一些您选择的类型的对象并将其转储到 Rc.

let x = Rc::new(5i32);

现在,您可以轻松创建另一个 Rc 指向完全相同的对象,因此指向内存位置:

let y = x.clone();
let yval: i32 = *y;

由于在 Rust 中您可能永远不会对内存位置有任何其他引用存在的可变引用,因此这些 Rc 容器永远无法再次修改。

那么,如果您希望能够修改那些 有多个 Rc 指向同一个对象的对象怎么办?

这是CellRefCell解决的问题。该解决方案称为 "interior mutability",这意味着 Rust 的别名规则在运行时而不是编译时强制执行。

回到我们最初的例子:

let x = Rc::new(RefCell::new(5i32));
let y = x.clone();

要获得对您的类型的可变引用,请在 RefCell 上使用 borrow_mut

let yval = x.borrow_mut();
*yval = 45;

如果您已经借用了您的 Rc 指向可变或非可变的值,borrow_mut 函数将出现恐慌,因此会强制执行 Rust 的别名规则。

Rc<RefCell<T>> 只是 RefCell 的一个示例,还有许多其他合法用途。但是文档是正确的。如果有其他方法,就用它,因为编译器不能帮你推理RefCells.

询问什么时候应该使用 CellRefCell 而不是 BoxRc 是不完全正确的,因为这些类型解决不同的问题。事实上,RefCell 通常 Rc 一起使用 以提供具有共享所有权的可变性。所以是的,CellRefCell 的用例完全取决于代码中的可变性要求。

内部和外部可变性在 designated chapter on mutability 的官方 Rust 书中有很好的解释。外部可变性与所有权模型密切相关,大多数情况下,当我们说某物可变或不可变时,我们指的正是外部可变性。外部可变性的另一个名称是 inherited mutability,这可能更清楚地解释了这个概念:这种可变性由数据的所有者定义,并继承到您可以从所有者那里获得的一切。例如,如果您的结构类型变量是可变的,那么变量中结构的所有字段也是可变的:

struct Point { x: u32, y: u32 }

// the variable is mutable...
let mut p = Point { x: 10, y: 20 };
// ...and so are fields reachable through this variable
p.x = 11;
p.y = 22;

let q = Point { x: 10, y: 20 };
q.x = 33;  // compilation error

继承的可变性还定义了您可以从值中获取哪些类型的引用:

{
    let px: &u32 = &p.x;  // okay
}
{
    let py: &mut u32 = &mut p.x;  // okay, because p is mut
}
{
    let qx: &u32 = &q.x;  // okay
}
{
    let qy: &mut u32 = &mut q.y;  // compilation error since q is not mut
}

然而,有时候,继承的可变性是不够的。典型的例子是引用计数指针,在 Rust 中称为 Rc。以下代码完全有效:

{
    let x1: Rc<u32> = Rc::new(1);
    let x2: Rc<u32> = x1.clone();  // create another reference to the same data
    let x3: Rc<u32> = x2.clone();  // even another
}  // here all references are destroyed and the memory they were pointing at is deallocated

乍一看不清楚可变性与此有何关系,但回想一下,引用计数指针之所以如此调用,是因为它们包含一个内部引用计数器,当引用被复制时该计数器会被修改(clone() 在 Rust 中)并销毁(在 Rust 中超出范围)。因此 Rc 必须 修改自身,即使它存储在非 mut 变量中。

这是通过内部可变性实现的。标准库中有特殊类型,其中最基本的是 UnsafeCell,它允许人们绕过外部可变性规则并改变某些东西,即使它存储(传递地)在非 [=26] =]变量。

另一种表示某物具有内部可变性的方式是,该某物可以通过 & 引用进行修改——也就是说,如果您有一个 &T 类型的值,并且您可以修改它指向的 T 的状态,那么 T 具有内部可变性。

例如,Cell 可以包含 Copy 数据,即使它存储在非 mut 位置也可以被变异:

let c: Cell<u32> = Cell::new(1);
c.set(2);
assert_eq!(c.get(), 2);

RefCell 可以包含非 Copy 数据,它可以为您提供指向其包含值的 &mut 指针,并且在运行时检查是否存在别名。这些在他们的文档页面上都有详细解释。


事实证明,在绝大多数情况下,您可以轻松地只使用外部可变性。 Rust 中的大多数现有高级代码都是以这种方式编写的。然而,有时内部可变性是不可避免的或使代码更清晰。一个例子,Rc 实现,上面已经描述过了。另一个是当您需要共享可变所有权时(即,您需要从代码的不同部分访问和修改相同的值)——这通常是通过 Rc<RefCell<T>> 实现的,因为它不能通过引用来完成独自的。另一个例子是 Arc<Mutex<T>>Mutex 是另一种内部可变性类型,它也可以安全地跨线程使用。

因此,如您所见,CellRefCell 不能替代 RcBox;他们解决了在默认情况下不允许的地方为您提供可变性的任务。您完全可以在不使用它们的情况下编写代码;如果您遇到需要它们的情况,您就会知道。

Cells 和 RefCells 不是代码味道;它们被描述为 "last resort" 的唯一原因是它们将检查可变性和别名规则的任务从编译器转移到运行时代码,例如 RefCell:你不能有两个 &muts 同时指向相同的数据,这是由编译器静态强制执行的,但是使用 RefCells 你可以要求相同的 RefCell 给你尽可能多的 &muts 随便你 - 除了如果你不止一次这样做它会恐慌你,在运行时强制执行别名规则。恐慌可以说比编译错误更糟糕,因为您只能在运行时而不是编译时找到导致它们的错误。然而,有时编译器中的静态分析器限制太多,您确实需要 "work around" 它。