dth
dth

Reputation: 2337

Why can Cell in Rust only be used for Copy and not Clone types?

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

Answers (3)

Jack O&#39;Connor
Jack O&#39;Connor

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

Shepmaster
Shepmaster

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

user395760
user395760

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:

  1. Cells (together with Option) allow creating cycles, i.e., self-referential types
  2. The Clone implementation gets a &self reference
  3. In the presence of a cycle, the Clone implementation may thus access the cell that's being cloned
  4. Therefore the object being cloned can overwrite itself, while it has an ordinary borrow to itself (namely, &self)
  5. Overwriting while borrowing is unsound because it allows arbitrary type punning and other badness. For example, suppose there's 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

Related Questions