当我可以使用Cell或RefCell时,我应该选择哪个?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当我可以使用Cell或RefCell时,我应该选择哪个?相关的知识,希望对你有一定的参考价值。

std::cell documentation,我看到Cell“只与实现Copy的类型兼容”。这意味着我必须将RefCell用于非Copy类型。

当我确实有Copy类型时,使用一种类型的细胞而不是另一种细胞是否有益处?我假设答案是“是”,因为否则两种类型都不存在!使用一种类型而不是另一种类型有什么好处和权衡?

这是一个愚蠢的,用CellRefCell来实现相同目标的例子:

use std::cell::{Cell,RefCell};

struct ThingWithCell {
    counter: Cell<u8>,
}

impl ThingWithCell {
    fn new() -> ThingWithCell {
        ThingWithCell { counter: Cell::new(0) }
    }

    fn increment(&self) {
        self.counter.set(self.counter.get() + 1);
    }

    fn count(&self) -> u8 { self.counter.get() }
}

struct ThingWithRefCell {
    counter: RefCell<u8>,
}

impl ThingWithRefCell {
    fn new() -> ThingWithRefCell {
        ThingWithRefCell { counter: RefCell::new(0) }
    }

    fn increment(&self) {
        let mut counter = self.counter.borrow_mut();
        *counter = *counter + 1;
    }

    fn count(&self) -> u8 { *self.counter.borrow_mut() }
}


fn main() {
    let cell = ThingWithCell::new();
    cell.increment();
    println!("{}", cell.count());

    let cell = ThingWithRefCell::new();
    cell.increment();
    println!("{}", cell.count());
}
答案

我认为考虑CellRefCell之间的其他语义差异很重要:

  • Cell为您提供值,RefCell与参考
  • Cell从不恐慌,RefCell可以恐慌

让我们想象一下这些差异很重要的情况:

let cell = Cell::new(foo);
{
    let mut value = cell.get();
    // do some heavy processing on value
    cell.set(value);
}

在这种情况下,如果我们想象一些具有大量回调的复杂工作流并且cell是全局状态的一部分,那么cell的内容可能被修改为“重处理”的副作用,并且这些潜在的变化当value被写回cell时,将会丢失。

另一方面,使用RefCell的类似代码:

let cell = RefCell::new(foo);
{
    let mut_ref = cell.borrow_mut().unwrap();
    // do some heavy processing on mut_ref
}

在这种情况下,任何修改cell作为“重处理”的副作用是被禁止的,并且会导致恐慌。因此,您确定cell的值不会在不使用mut_ref的情况下发生变化

我会根据它所持有的值的语义决定使用哪个,而不仅仅是Copy特性。如果两者都可以接受,那么Cell比另一种更轻,更安全,因此更为可取。

另一答案

如果可以,你应该使用Cell

Cell根本不使用运行时检查。它所做的只是一个不允许别名的封装,并告诉编译器它是一个内部可变的插槽。在大多数情况下,它应编译为与没有单元格包装的类型完全相同的代码。

相比之下,RefCell使用一个简单的使用计数器来检查借用与运行时的可变借用,如果你违反了例如可变借用的排他性,那么检查可能会在运行时导致恐慌。可能的恐慌可能是优化的障碍。

至少还有一个区别。 Cell永远不会让你得到一个指向存储值本身的指针。所以,如果你需要,RefCell是唯一的选择。

另一答案

TL; DR:Cell,你可以。


答案很长:CellRefCell有一个相似的名字,因为它们都允许内部可变性,但它们有不同的目的:

Cell

它是T的一个包装,它禁止一次多次共享它:你不能不可靠地借用内部数据。此包装器没有任何开销,但由于此限制,您只能执行以下操作:

  • 设置内部值,
  • 用其他东西交换内在价值,
  • 复制内部值(仅当TCopyable时)。

由于它的限制,Cell表现得像一个独家借用,又名&mut T。因此,改变内在值总是安全的。总结一下:

  • 优点:没有开销
  • 优点:总是可变的
  • 限制:有些操作是不可能的

 RefCell

它是T的一个包装器,它“删除”编译时借用检查:修改内部值的操作将共享引用&self带到RefCell。通常,这将是不安全的,但是每个修改操作首先验证该值以前未被借用。可变借用的排他性在运行时得到验证。

总结一下:

  • 限制:非常小的开销
  • 限制:并非总是可变的,如果以前是可变的借用(注意,在这种情况下某些操作可能会出现恐慌)
  • 优点:您不限于您可以执行的操作

What should you chose?

优点和局限是彼此的一面镜子。您的问题的答案是:如果Cell的限制不打扰您,请使用它,因为除此之外,它只有优势。但是,如果您想要更灵活的内部可变性,请使用RefCell

以上是关于当我可以使用Cell或RefCell时,我应该选择哪个?的主要内容,如果未能解决你的问题,请参考以下文章

Rayon 如何防止线程之间使用 RefCell<T>、Cell<T> 和 Rc<T>?

是否有替代方法或方法让 Rc<RefCell<X>> 限制 X 的可变性?

如何正确访问 RefCell 中的值

如何调用具有泛型类型的结构的关联函数?

如何在不破坏封装的情况下返回对 RefCell 内某些内容的引用?

如何借用 RefCell<HashMap>、找到键并返回对结果的引用? [复制]