I have been using for some time by now and I never needed Ref or Refcell am i missing something?

If I need to pass and update things around I always use Arc and I can’t say I quite understand the point of Refcell. Can you please explain why would I need an Rc instead of Arc or are they completely different things and I misunderstood the entire thing wrong?

  • Arc is a wrapper for shared ownership (i.e., used to solve lifetimes issues), by using atomic counters so that the “wrappee” is only dropped when the last owner is dropped;

  • Rc has the same functionality (shared ownership), except that the internal counters it uses do not have to be atomic. This means that:

    • it cannot be used in multi-threaded scenarios (not Send nor Sync);

    • in a single threaded scenario it can be faster than Arc;

  • RefCell is a wrapper used to solve cannot borrow mutably… issues, by providing Interior Mutability, which allows modifying a field of a struct through a shared reference &_ to that struct (mutation despite aliasing). This is possible by replacing the compile-time invariants that Rust provides with runtime checks that ensure / enforce these invariants (it uses counters too, to track the amount of Ref and RefMut borrows). It should be noted that the way RefCell handles misusage is by panic!king, which cannot cross thread boundaries, thus making it not usable in multi-threaded scenarios (it is not Sync);

  • RwLock is the multi-threaded equivalent of RefCell and its Interior Mutability: it uses atomic counters and instead of panic!king it just waits for the counters to reach the right values, hence acting as a lock.

    • Mutex acts in a similar fashion (to RwLock), but without the capacity to provide multiple readers. It can only provide single writer accesses (i.e., a RefMut in RefCell parlance), which are exclusive.
  • Finally, when the wrappee is just a plain integer, and you wish to use Interior Mutability with it, using RefCell / RwLock is overkill: for increased performance, you can use Atomic{Integer}, or Cell<{integer}> (faster but only for single-threaded usage).


Addendum

Regarding Rc and Arc, since they are used for shared ownership and thus aliasing, they do lend shared / aliased references (&_) but do not lend unique / exclusive references (&mut _).

  • When you suspect that your shared ownership is actually unique, you can try to get a unique reference through the get_mut or make_mut methods (whose behaviors only differ when the hypothesis is wrong and there are multiple owners: if that is the case, the former fails, returning None, whereas the latter clones, hence guaranteeing indeed uniqueness)

That’s why the only way to get both shared ownership (with its inherent aliasing) and mutation is through Interior Mutability, hence leading to two very common “wrapping patterns”; let’s see if you can guess them:

  • Single-threaded

    Click here to view the answer

    Rc<RefCell<_>>

    • (or Rc<Cell<{integer}>>)
  • Multi-threaded

    Click here to view the answer

    Arc<RwLock<_>>

    • (or Arc<Atomic{Integer}>)
12 Likes

Short answer for your second question: Rc is just a micro-optimization to avoid the perf impact of coherent memory when you’re only in one thread. You never need to use it.

2 Likes