Cell或RefCell是最佳选择的情况

时间:2015-06-14 15:16:13

标签: rust interior-mutability

您何时需要使用Cell or RefCell?似乎有许多其他类型的选择可以代替这些,文档警告说使用RefCell有点“最后的手段”。

使用这些类型是“code smell”吗?任何人都可以展示使用这些类型比使用其他类型更有意义的示例,例如Rc甚至Box

3 个答案:

答案 0 :(得分:29)

要求CellRefCell在[{1}}和Box上使用时,并不完全正确,因为这些类型解决了不同的问题。实际上,Rc通常与RefCell一起使用,以便为共享所有权提供可变性。是的,RcCell的用例完全取决于代码中的可变性要求。

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

RefCell

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

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

然而,有时候,继承的可变性是不够的。规范示例是引用计数指针,在Rust中称为{ 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 } 。以下代码完全有效:

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 in Rust)并销毁(超出clone()中的范围)。因此,Rust 具有来修改自身,即使它存储在非Rc变量中。

这是通过内部可变性实现的。标准库中有一些特殊的类型,其中最基本的类型是UnsafeCell,它允许一个人解决外部可变性的规则,并且即使它被存储(传递)在非{{1}中也会变异。变量。

另一种说法是内部可变性的方法是可以通过mut - 引用来修改这个内容 - 也就是说,如果你有mut类型的值,你可以修改状态它所指向的&,然后&T具有内部可变性。

例如,T可以包含T数据,即使数据存储在非Cell位置,也可以进行变更:

Copy

mut可以包含非let c: Cell<u32> = Cell::new(1); c.set(2); assert_eq!(c.get(), 2); 数据,它可以为您提供指向其包含值的RefCell指针,并且在运行时检查是否存在别名。这些都在他们的文档页面上详细解释。

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

因此,正如您所看到的,Arc<Mutex<T>>Mutex不是CellRefCell的替代品;他们解决了在默认情况下不允许的地方为您提供可变性的任务。您可以编写代码而不使用它们;如果你遇到需要它们的情况,你就会知道它。

RcBox s不是代码气味;将它们描述为“最后的手段”的唯一原因是它们将检查可变性和别名规则的任务从编译器移动到运行时代码,就像Cell一样:你不能有两个{{ 1}}同时指向相同的数据,这是由编译器静态强制执行的,但使用RefCell s你可以让同一个RefCell给你尽可能多的&mut你喜欢的 - 除了如果你不止一次这样做它会让你感到恐慌,在运行时强制执行别名规则。恐慌可能比编译错误更糟糕,因为您只能在运行时而不是在编译时发现导致错误的错误。但是,有时编译器中的静态分析器限制性太强,而且确实需要“解决”它。

答案 1 :(得分:8)

不,CellRefCell不是&#39;#34;代码闻起来&#34;。通常,可变性是继承的,也就是说,当且仅当您拥有对整个数据结构的独占访问权时,您才可以改变字段或数据结构的一部分,因此您可以选择变为可变性与mut相关的级别(即foo.x foo继承其可变性或缺少其可变性。这是一个非常强大的模式,应该在它运行良好时使用(经常令人惊讶)。但它对所有代码都没有足够的表现力。

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

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

这可以通过Rc<RefCell<T>>的通用组合来完成,它允许数据在任何人使用它时保持不变,并允许每个人(但一次只有一个!)来改变它。或者它可以像&Cell<i32>一样简单(即使单元格包含在更有意义的类型中)。后者也常用于内部,私有,可变状态之类的引用计数。

文档实际上有几个例子,说明您使用CellRefCell的位置。一个很好的例子实际上是Rc本身。创建新的Rc时,必须增加引用计数,但引用计数在所有Rc之间共享,因此,通过继承的可变性,这可能无法正常工作。 Rc几乎 使用Cell

一个好的指导方针是尝试编写尽可能多的代码而不使用单元格类型,但在没有单元格类型的情况下使用它们时会使用它们。在某些情况下,有一个没有细胞的好解决方案,而且,根据经验,你可以在以前错过它们的时候找到它们,但总有一些东西在没有它们的情况下是不可能的。

答案 2 :(得分:7)

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

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

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

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

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

那么,如果你想修改那些这些对象有多个Rc指向同一个对象怎么办?

这是CellRefCell解决的问题。该解决方案被称为“内部可变性”,这意味着Rust的别名规则在运行时而不是编译时强制执行。

回到我们原来的例子:

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

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

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

如果您已经将Rc s的值借用了可变或不可变的值,borrow_mut函数将会出现混乱,从而强制执行Rust的别名规则。

Rc<RefCell<T>>只是RefCell的一个示例,还有许多其他合法用途。但文档是对的。如果有其他方法,请使用它,因为编译器无法帮助您推理RefCell s。