🎓 A1Learner
🔍

Rust Rc & RefCell

Rc is a smart pointer that counts its owners, so one value can have several owners at once. RefCell lets a shared value change and checks the borrow rules while the program runs.

The pain: one tail, two lists, plain Box

With Box, the tail moves into the first list. The second list is left with nothing:

enum List {
    Cons(i32, Box<List>),
    Nil,
}
use List::{Cons, Nil};
let tail = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let a = Cons(3, Box::new(tail));
let b = Cons(4, Box::new(tail));
// error[E0382]: use of moved value: `tail`

Clone the pointer, not the data

Rc::clone only raises the count. And when an Rc goes out of scope, its Drop code lowers the count by one. It does this by itself. You never lower it by hand.

use std::rc::Rc;
let shared = Rc::new(10);
let _copy = Rc::clone(&shared);
println!("owners = {}", Rc::strong_count(&shared));

Two lists, one shared tail

With Box this fails: the tail moves into the first list, and the second use is error E0382 (checked). Rc::clone lets both lists point at the same tail.

let tail = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let a = Cons(3, Rc::clone(&tail));
let b = Cons(4, Rc::clone(&tail));
println!("owners = {}", Rc::strong_count(&tail));

Change the shared value with make_mut

With several owners, make_mut first clones the value, so the other owners keep the old one — clone-on-write. With one owner, it edits in place.

let mut data = Rc::new(vec![1, 2]);
let other = Rc::clone(&data);
Rc::make_mut(&mut data).push(3);
println!("data = {:?} other = {:?}", data, other);

Ask for unique access with get_mut

get_mut gives Some only when no other Rc points at the value. No Weak may point at it either. It gives None while the value is shared.

let mut only = Rc::new(5);
println!("get_mut = {:?}", Rc::get_mut(&mut only));

RefCell: the rules, checked live

borrow() reads and borrow_mut() writes. Two live writers do not fail to compile — the program panics while running, with the message "already borrowed" (checked).

use std::cell::RefCell;
let cell = RefCell::new(vec![1, 2]);
cell.borrow_mut().push(3);
println!("{:?}", cell.borrow());

Example

use std::rc::Rc; use std::cell::RefCell;
fn main() {
    let shared = Rc::new(RefCell::new(vec![1, 2]));
    let clone = Rc::clone(&shared);
    clone.borrow_mut().push(3);
    println!("{:?} owners={}", shared.borrow(), Rc::strong_count(&shared));
}

Key points

  • Cell<T> is the simpler cousin for single copied values
  • Rc<T> on its own gives only shared, read-only access — that limit is exactly why the Rc<RefCell<T>> pairing exists
  • Their counting is not safe across threads.
  • The Concurrency section introduces their thread-safe twins, Arc and Mutex.
  • Rc keeps one value alive for several owners by counting those owners.

In this example, shared is an Rc pointing to a RefCell holding a Vec of [1, 2]. Rc::clone makes a second owner, clone, for the same Vec — the list itself is not copied. Through clone, borrow_mut() takes the one allowed write borrow and pushes 3. Then shared.borrow() takes a read borrow to print the Vec: [1, 2, 3]. Both owners see the change, because there is only ever one Vec. strong_count reports 2 owners. Weak, a pointer that does not count as an owner, breaks cycles in Rust Reference Cycles.

Note

Rc and RefCell are for one thread only.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What happens if two borrow_mut() guards from one RefCell are alive at the same time?