59

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());
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366

3 Answers3

62

I think it is important to take into account the other semantic differences between Cell and RefCell:

  • Cell provides you values, RefCell with references
  • Cell never panics, RefCell can panic

Let us imagine a situation where these differences matter:

let cell = Cell::new(foo);
{
    let mut value = cell.get();
    // do some heavy processing on value
    cell.set(value);
}

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 of cell are modified as a side effect of the "heavy processing", and these potential changes will be lost when value is written back in cell.

On the other hand, a similar code using RefCell:

let cell = RefCell::new(foo);
{
    let mut_ref = cell.borrow_mut().unwrap();
    // do some heavy processing on mut_ref
}

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 of cell will not change without using mut_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, then Cell is lighter and safer than the other, and thus would be preferable.

Levans
  • 14,196
  • 3
  • 49
  • 53
  • 2
    You can get value semantics with `RefCell` though: clone the initial state, work on it, and write back the modified state into the cell at the end. – Matthieu M. May 16 '15 at 13:32
  • 1
    @MatthieuM. but you cannot get reference semantics with `Cell`, correct? – Shepmaster May 16 '15 at 16:55
  • 2
    @Shepmaster: Well, you can have references to the `Cell`. If you want a reference to the value you can use its `UnsafeCell` constituent, and the `UnsafeCell` can give you a pointer to the reference value... but it's unsafe (as per the name). So, no, `Cell` is not meant for manipulating references; it would not be safe otherwise, as it breaks out of the borrowing checks. – Matthieu M. May 16 '15 at 17:59
  • 3
    @MatthieuM. Indeed, `RefCell` allows you to get value semantics on data that is only `Clone` and not `Copy`, *as long as it is not borrowed elsewhere*. However, it also allows you to *force* reference semantics on `Copy` data, which was my point, as the question was supposing the type is `Copy`. – Levans May 16 '15 at 18:20
41

TL; DR: Cell when you can.


Long answer: Cell and RefCell 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:

  • Set the inner value,
  • Swap the inner value with something else,
  • Copy the inner value (only when T is Copyable, 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:

  • Advantage: no overhead
  • Advantage: always mutable
  • Limitation: some operations are impossible

 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 the RefCell. 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:

  • Limitation: a very small overhead
  • Limitation: not always mutable, if it was previously mutably borrowed (beware, some operations may panic in this case)
  • Advantage: you are not limited with the operations that you can do

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, use RefCell.

Community
  • 1
  • 1
Boiethios
  • 38,438
  • 19
  • 134
  • 183
29

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, a RefCell is the only choice.

nbro
  • 15,395
  • 32
  • 113
  • 196
bluss
  • 12,472
  • 1
  • 49
  • 48
  • 1
    Where can I learn more about how it disallows aliasing? Can it use this to declare it as `noalias`? https://github.com/rust-lang/rust/issues/31681 seems to hint at a related feature from `&mut` pointers. But couldn't any `unsafe` block potentially alias? If I write an `unsafe` block is it up to me to make sure I don't create an aliasing reference or dereference anything that aliases a `Cell` somewhere? – Brian Cain Mar 11 '17 at 00:31
  • 4
    How it disallows aliasing: It doesn't let you get a reference (thus no pointer at all) to the value **in** the Cell. The last question is easy to answer: Yes, it is up to you, unsafe blocks are an explicit “trust the programmer” escape hatch. As the saying goes `unsafe` blocks are not used to break Rust's invariants, but to uphold them manually. – bluss Mar 11 '17 at 10:30
  • 1
    Is it just me or is that particular facet of the onus of `unsafe` blocks really subtle? – Brian Cain Mar 11 '17 at 18:37
  • 1
    There are many reasons to avoid `unsafe` at all costs. You put yourself in position of responsibility for one, and another good reason is that the work to formalize the requirements on unsafe code blocks is not yet complete! It's a trust the programmer mode, in a language where we usually want the compiler to check our work. – bluss Mar 11 '17 at 20:07
  • 1
    Note that aliasing is normally reasoned about in terms of writing through one pointer while having another pointer that assumes the pointee doesn't change. Just pointer equality without any writes is not what we think of. – bluss Mar 11 '17 at 20:08
  • @bluss "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, a RefCell is the only choice." I think this is not strictly true: There is a `get_mut` method on cells which returns a `&mut T`, but this relies on the Cell itself being mutable, which is not usually the case. The docs for `RefCell` highlight this where the docs for `Cell` do not. – Eosis Jun 17 '21 at 08:57