Reputation: 2337
The documentation of the Rust standard library states that Cell
can be only used for Copy
types and that in all other cases one should use RefCell
, but does not explain exactly why.
After studying the documentation and the code of both Cell
and RefCell
, the only point where it seems to be important is the get
function of Cell
. If the value is a Copy
type then one can just return such a copy. But why is a clone not good enough?
One could directly implement the set
function on top of RefCell
:
fn set<T>(r: &RefCell<T>, v: T) {
*r.borrow_mut() = v
}
This only works as long as no one else is holding a reference to the value. But if the value can be cloned, one can just do that:
fn get<T: Clone>(r: &RefCell<T>) -> T {
r.borrow().clone()
}
Having a type like Cell
working with Clone
types would avoid the overhead of the run-time borrow checking. Am I missing anything here?
Upvotes: 18
Views: 3506
Reputation: 10926
The accepted answer is still completely correct (and fascinating), but I want to mention a few extra tools that Cell
got in Rust 1.17, which don't require the contents to be Copy
:
Cell::swap
exchanges the contents of two cells.Cell::replace
puts a new value in a cell and returns the old value.Cell::take
is like replace
, using the Default::default()
value.Note the close parallel here with mem::swap
, mem::replace
, and mem::take
. (Though actually the last one didn't get stabilized until Rust 1.40.) The Cell
methods do effectively the same thing, but they work through shared references instead of requiring mutable ones.
For types that implement Default
, we can use Cell::take
to accomplish something very similar to .clone()
, just with a few more steps:
fn clone_from_cell<T>(cell: &Cell<T>) -> T
where
T: Clone + Default,
{
let val: T = cell.take();
let clone: T = val.clone();
cell.set(val);
clone
}
And for types that implement Clone
but not Default
(somewhat uncommon, but for example NonZeroU32
), note that Option<T>
implements Default
regardless of T
, so Cell<Option<T>>
can be "cloned" using that function for any T
.
Upvotes: 3
Reputation: 431589
Here is my opinion, but I can't tie it directly to a real reason that such a restriction exists.
I think of a copy as "cheap" (e.g. copying a handful of bits) and a clone as "expensive" (e.g. making a function call or changing data). If such a cell used Clone
, it would mandate that the underlying value be duplicated on every use (cell.get()
). For example, using a CloneCell<Vec<T>>
would mean that every cell.get()
would require calling the memory allocator. That's not a good idea.
Restricting to Copy
types is thus potentially a way to guide people away from shooting themselves in the foot.
Upvotes: 2
Reputation:
It's unsound. The comment by DK. is on the right track but you don't even need a panic to cause havoc. One problematic scenario is this:
Option
) allow creating cycles, i.e., self-referential typesClone
implementation gets a &self
referenceClone
implementation may thus access the cell that's being cloned&self
)Result<T, E>
field that's initially Ok(T)
, take a reference to the T
inside and overwrite the Result
with an Err(R)
. Then the &T
suddenly refers to an E
value.Credit for this example goes to Huon Wilson, see the user.rust-lang.org thread Why does Cell require Copy instead of Clone?. His write-up goes into more structural reasons for the restrictions and includes a complete code example, too.
Upvotes: 13