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

She*_*ter 27 rust

std::cell文档中,我看到Cell"只与实现的类型兼容Copy".这意味着我必须使用RefCellCopy类型.

当我这样做有一个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());
}
Run Code Online (Sandbox Code Playgroud)

Lev*_*ans 23

我认为考虑到Cell和之间的其他语义差异很重要RefCell:

  • Cell为您RefCell提供参考值
  • Cell永远不会恐慌,RefCell可以恐慌

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

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

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

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

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

在这种情况下,任何cell对"重处理"的副作用的修改都是被禁止的,并且会导致恐慌.因此,您可以确定,cell如果不使用,价值就不会改变mut_ref

我将决定使用哪个取决于它所持有的值的语义,而不仅仅是Copy特征.如果两者都是可接受的,则Cell比另一种更轻且更安全,因此是优选的.

  • @马修M。事实上,“RefCell”允许您获取仅“Clone”而非“Copy”的数据的值语义,*只要它没有在其他地方借用*。然而,它还允许您*强制*对“复制”数据引用语义,这是我的观点,因为问题是假设类型是“复制”。 (3认同)
  • 不过,您可以使用“RefCell”获得值语义:克隆初始状态,对其进行处理,最后将修改后的状态写回到单元格中。 (2认同)
  • @Shepmaster:嗯,你可以引用`Cell`。如果你想引用这个值,你可以使用它的 `UnsafeCell` 成分,`UnsafeCell` 可以给你一个指向引用值的指针......但它是不安全的(根据名称)。所以,不,`Cell` 不是用来操作引用的;否则它就不安全了,因为它脱离了借用支票。 (2认同)

blu*_*uss 15

Cell如果可以,你应该使用.

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

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

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

  • 它如何禁止别名:它不会让你在**单元格中获得值**的引用(因此根本没有指针).最后一个问题很容易回答:是的,这取决于你,不安全的块是一个明确的"信任程序员"逃生舱.俗话说"不安全"块不是用来破坏Rust的不变量,而是用来手动维护它们. (2认同)

Fre*_*ios 7

TL; DRCell如果可以的话。


长答案CellRefCell具有类似的名称,因为它们都允许内部可变性,但是它们具有不同的用途:

Cell

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

  • 设置内部值
  • 用其他东西交换内在价值,
  • 复制内的值(仅当TCopy能,因而)。

由于其局限性,Cell行为就像独占借名,也称为 a &mut T。因此,更改内部值始终是安全的。总结一下:

  • 优点:无开销
  • 优势:总是可变的
  • 局限性:某些操作是不可能的

 RefCell

它是围绕一个包装T该“清除”编译时借位检查:用于修改所述内值取一个共享参照的操作&selfRefCell。通常,这是不安全的,但是每个修改操作首先都会验证该值先前是否未被借用。在运行时验证可变借位的排他性。

总结一下:

  • 局限性:开销很小
  • 局限性:并非总是可变的,如果它以前是可变借来的(请注意,在这种情况下某些操作可能会惊慌)
  • 优势:您不受限于可以执行的操作

您应该选择什么?

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