当我可以使用 Cell 或 RefCell 时,我应该选择哪个?
When I can use either Cell or RefCell, which should I choose?
从 std::cell
documentation 中,我看到 Cell
是 "only compatible with types that implement Copy
"。这意味着我必须对非 Copy
类型使用 RefCell
。
当我 有一个 Copy
类型时,使用一种类型的单元格比使用另一种类型的单元格有好处吗?我假设答案是 "yes",否则两种类型都不会存在!使用一种类型相对于另一种类型的好处和权衡是什么?
这是一个愚蠢的虚构示例,它使用 Cell
和 RefCell
来实现相同的目标:
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());
}
如果可以,您应该使用 Cell
。
Cell
根本不使用运行时检查。它所做的只是一个不允许别名的封装,并告诉编译器它是一个内部可变槽。在大多数情况下,它应该编译成与没有单元格包装的类型完全相同的代码。
相比之下,RefCell
在运行时使用一个简单的使用计数器来检查借用与可变借用,并且如果您违反可变借用的排他性等,该检查可能会导致运行时出现恐慌。可能的恐慌可能会阻碍优化。
至少还有一处不同。 Cell
永远不会让您获得指向存储值本身的指针。所以,如果你需要,RefCell
是唯一的选择。
我认为考虑 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
是全局状态的一部分,则 cell
的内容可能会被修改为"heavy processing" 的副作用,当 value
写回 cell
时,这些潜在的变化将丢失。
另一方面,类似的代码使用 RefCell
:
let cell = RefCell::new(foo);
{
let mut_ref = cell.borrow_mut().unwrap();
// do some heavy processing on mut_ref
}
在这种情况下,作为 "heavy processing" 的副作用对 cell
的任何修改都是被禁止的,并且会导致恐慌。因此,您可以确定 cell
的值在不使用 mut_ref
的情况下不会改变
我会根据它所持有的值的语义来决定使用哪个,而不仅仅是 Copy
特征。如果两者都可以接受,那么 Cell
比另一个更轻、更安全,因此更可取。
TL; DR:Cell
当你可以的时候。
长答案:Cell
和 RefCell
具有相似的名称,因为它们都允许内部可变性,但它们有不同的目的:
Cell
它是 T
的包装器,禁止一次多次共享它:您不能不可变地借用内部数据。这个包装器没有任何开销,但是由于这个限制,你只能做以下操作:
- 设置内在值,
- 用其他东西交换内部值,
- 复制内部值(仅当
T
是 Copy
时,因此)。
由于其限制,Cell
的行为类似于独占借用,aka a &mut T
。因此,更改内在值总是安全的。总结一下:
- 优点:无开销
- 优点:始终可变
- 限制:部分操作无法进行
RefCell
它是 T
的包装器,“删除”了编译时借用检查:修改内部值的操作采用对 RefCell
的共享引用 &self
.通常,这是不安全的,但每个修改操作首先验证该值之前没有被借用。可变借用的排他性在运行时进行验证。
总结:
- 限制:开销很小
- 限制:不总是可变的,如果它以前是可变借用的(注意,在这种情况下某些操作可能会崩溃)
- 优点:您可以进行的操作不受限制
你应该选择什么?
优点和局限互为镜子。您的问题的答案是:如果 Cell
的限制不打扰您,请使用它,因为除此之外,它只有优点。但是,如果您想要更灵活的内部可变性,请使用 RefCell
.
从 std::cell
documentation 中,我看到 Cell
是 "only compatible with types that implement Copy
"。这意味着我必须对非 Copy
类型使用 RefCell
。
当我 有一个 Copy
类型时,使用一种类型的单元格比使用另一种类型的单元格有好处吗?我假设答案是 "yes",否则两种类型都不会存在!使用一种类型相对于另一种类型的好处和权衡是什么?
这是一个愚蠢的虚构示例,它使用 Cell
和 RefCell
来实现相同的目标:
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());
}
如果可以,您应该使用 Cell
。
Cell
根本不使用运行时检查。它所做的只是一个不允许别名的封装,并告诉编译器它是一个内部可变槽。在大多数情况下,它应该编译成与没有单元格包装的类型完全相同的代码。
相比之下,RefCell
在运行时使用一个简单的使用计数器来检查借用与可变借用,并且如果您违反可变借用的排他性等,该检查可能会导致运行时出现恐慌。可能的恐慌可能会阻碍优化。
至少还有一处不同。 Cell
永远不会让您获得指向存储值本身的指针。所以,如果你需要,RefCell
是唯一的选择。
我认为考虑 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
是全局状态的一部分,则 cell
的内容可能会被修改为"heavy processing" 的副作用,当 value
写回 cell
时,这些潜在的变化将丢失。
另一方面,类似的代码使用 RefCell
:
let cell = RefCell::new(foo);
{
let mut_ref = cell.borrow_mut().unwrap();
// do some heavy processing on mut_ref
}
在这种情况下,作为 "heavy processing" 的副作用对 cell
的任何修改都是被禁止的,并且会导致恐慌。因此,您可以确定 cell
的值在不使用 mut_ref
我会根据它所持有的值的语义来决定使用哪个,而不仅仅是 Copy
特征。如果两者都可以接受,那么 Cell
比另一个更轻、更安全,因此更可取。
TL; DR:Cell
当你可以的时候。
长答案:Cell
和 RefCell
具有相似的名称,因为它们都允许内部可变性,但它们有不同的目的:
Cell
它是 T
的包装器,禁止一次多次共享它:您不能不可变地借用内部数据。这个包装器没有任何开销,但是由于这个限制,你只能做以下操作:
- 设置内在值,
- 用其他东西交换内部值,
- 复制内部值(仅当
T
是Copy
时,因此)。
由于其限制,Cell
的行为类似于独占借用,aka a &mut T
。因此,更改内在值总是安全的。总结一下:
- 优点:无开销
- 优点:始终可变
- 限制:部分操作无法进行
RefCell
它是 T
的包装器,“删除”了编译时借用检查:修改内部值的操作采用对 RefCell
的共享引用 &self
.通常,这是不安全的,但每个修改操作首先验证该值之前没有被借用。可变借用的排他性在运行时进行验证。
总结:
- 限制:开销很小
- 限制:不总是可变的,如果它以前是可变借用的(注意,在这种情况下某些操作可能会崩溃)
- 优点:您可以进行的操作不受限制
你应该选择什么?
优点和局限互为镜子。您的问题的答案是:如果 Cell
的限制不打扰您,请使用它,因为除此之外,它只有优点。但是,如果您想要更灵活的内部可变性,请使用 RefCell
.