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

时间:2015-05-16 13:01:53

标签: rust

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());
}

3 个答案:

答案 0 :(得分:21)

我认为考虑char *pByte = (char*)pCoords + sizeof(COORDS) * i + offsetof(COORDS, area); double *pDouble = (pDouble*)pByte; *pDouble = 42.0; 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); } 是全局状态的一部分,则cell的内容可能会被修改为副作用&#34;重处理&#34;,当cell写回value时,这些潜在的更改将会丢失。

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

RefCell

在这种情况下,let cell = RefCell::new(foo); { let mut_ref = cell.borrow_mut().unwrap(); // do some heavy processing on mut_ref } 的任何修改都是&#34;重处理&#34;的副作用。是被禁止的,会导致恐慌。因此,您确定cell的值不会在不使用cell

的情况下发生变化

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

答案 1 :(得分:13)

如果可以,您应该使用Cell

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

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

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

答案 2 :(得分:5)

TL; DR Cell(如果可以)。


长答案CellRefCell的名称相似,因为它们都允许内部可变性,但目的却不同:

Cell

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

  • 设置内部值
  • 用其他东西交换内部价值
  • 复制内部值(仅当T可以Copy时复制)。

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

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

RefCell

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

总结:

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

您应该选择什么?

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