From the std::cell
documentation, I see that Cell
is "only compatible with types that implement Copy
". This means I must use RefCell
for non-Copy
types.
When I do have a Copy
type, is there a benefit to using one type of cell over another? I assume the answer is "yes", because otherwise both types wouldn't exist! What are the benefits and tradeoffs of using one type over the other?
Here's a silly, made-up example that uses both Cell
and RefCell
to accomplish the same goal:
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());
}
You should use
Cell
, if you can.Cell
uses no runtime checking at all. All it does is an encapsulation that disallows aliasing and tells the compiler that it is an internally mutable slot. In most cases, it should compile to code that is exactly the same as if the type without cell wrapping was there.By comparison,
RefCell
uses a simple usage counter to check borrowing vs. mutable borrowing at runtime, and that check can lead to a panic at runtime if you violate for example the exclusivity of mutable borrowing. The possible panic can be an impediment to optimization.There is at least one more difference. A
Cell
will never let you get a pointer to the stored value itself. So, if you need that, aRefCell
is the only choice.TL; DR:
Cell
when you can.Long answer:
Cell
andRefCell
have a similar name because they both permit the interior mutability, but they have a different purpose:Cell
It is a wrapper around
T
that forbids to share it multiple times at once: you cannot borrow immutably the inner data. This wrapper does not have any overhead, but because of this limitation, you can only do the following operations:T
isCopy
able, thus).Thanks to its limitation, the
Cell
behaves like an exclusive borrow, aka a&mut T
. Therefore, it is always safe to change the inner value. To summarize:RefCell
It is a wrapper around
T
that "removes" the compile-time borrow-checks: the operations that modify the inner value take a shared reference&self
to theRefCell
. Normally, this would be unsafe, but each modifying operation firstly verify that the value was not previously borrowed. The exclusivity of a mutable borrow is verified at runtime.To summarize:
What should you chose?
The advantages and limitations are a mirror of each other. The answer to your question is: if the limitations of
Cell
do not bother you, use it, because beside this, it has only advantages. However, if you want a more flexible interior mutability, useRefCell
.I think it is important to take into account the other semantic differences between
Cell
andRefCell
:Cell
provides you values,RefCell
with referencesCell
never panics,RefCell
can panicLet us imagine a situation where these differences matter:
In this case, if we imagine some complex workflow with a lot of callback and that
cell
is part of a global state, it is possible that the contents ofcell
are modified as a side effect of the "heavy processing", and these potential changes will be lost whenvalue
is written back incell
.On the other hand, a similar code using
RefCell
:In this case, any modification of
cell
as a side-effect of the "heavy processing" is forbidden, and would result into a panic. You thus are certain that the value ofcell
will not change without usingmut_ref
I would decide which to use depending of the semantics of the value it holds, rather than simply the
Copy
trait. If both are acceptable, thenCell
is lighter and safer than the other, and thus would be preferable.