RustArena

Box, Rc and RefCell in Rust: smart pointers explained

When to use Box<T>, Rc<T>, RefCell<T>, Rc<RefCell<T>> and Weak<T> in Rust, with the trade-offs and the runtime borrow panic to avoid.

A smart pointer is a type that acts like a reference but also owns and manages what it points to. You've already used two: String and Vec<T>. The standard library adds three you'll meet constantly.

Type Gives you Cost Typical use
Box<T> one owner, value on the heap one allocation recursive types, trait objects, large values
Rc<T> several owners (single thread), read-only allocation + reference count shared data in trees and graphs, caches
RefCell<T> mutation through a shared reference, checked at run time a borrow flag interior mutability
Rc<RefCell<T>> several owners that can all mutate both shared mutable state in one thread
Weak<T> a non-owning link to Rc data a second count parent pointers, breaking cycles
Arc<T>, Mutex<T> the thread-safe versions of Rc, RefCell atomic operations, locking sharing across threads

Box: single ownership on the heap

Box::new(x) moves x to the heap and gives you a pointer that owns it. The box frees the value when it's dropped. You need it for recursive types (a tree node containing nodes: without a box the type would have infinite size, error E0072) and for trait objects like Box<dyn Error> or Vec<Box<dyn Shape>>.

Rc: shared ownership

Rc (reference counted) lets several parts of a program own the same value. Rc::clone(&rc) doesn't copy the data, it increments a counter; the value is freed when the last Rc is dropped. Rc::strong_count(&rc) shows the count. The data inside is read-only:

use std::rc::Rc;

fn main() {
    let shared = Rc::new(vec![1, 2]);
    shared.push(3);
}
error[E0596]: cannot borrow data in an `Rc` as mutable

Rc is not thread-safe (its counter isn't atomic). The compiler stops you from sending it to another thread; use Arc there.

RefCell: borrow checking at run time

RefCell moves the borrowing rules from compile time to run time. .borrow() gives a shared borrow, .borrow_mut() a mutable one, both through a plain &RefCell<T>. This is interior mutability. The rules still apply: if you call borrow_mut() while another borrow is alive, the program panics with RefCell already borrowed. Keep borrows short (don't hold one across a call that might borrow again), or use try_borrow_mut() to get a Result.

For Copy values, Cell<T> is simpler: get() and set(), no borrows, no panics.

Rc<RefCell>: shared and mutable

Combine them when several owners must change the same data, such as observers sharing a log, or nodes in a graph. Each owner holds an Rc, and calls .borrow_mut() to change the value.

Weak: avoiding cycles

Two Rcs pointing at each other never reach a count of zero, so they leak. Make one direction weak: Rc::downgrade(&rc) gives a Weak, which doesn't keep the value alive; weak.upgrade() returns Option<Rc<T>>. Trees typically use Rc for children and Weak for the parent link.

Advice at work

Example

use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};

#[derive(Debug)]
enum Tree {
    Leaf(i32),
    Node(Box<Tree>, Box<Tree>),
}

fn sum(t: &Tree) -> i32 {
    match t {
        Tree::Leaf(v) => *v,
        Tree::Node(l, r) => sum(l) + sum(r),
    }
}

struct Team {
    name: String,
    members: RefCell<Vec<Rc<Player>>>,
}

struct Player {
    name: String,
    team: RefCell<Weak<Team>>,
    score: Cell<u32>,
}

fn main() {
    let tree = Tree::Node(Box::new(Tree::Leaf(1)), Box::new(Tree::Node(Box::new(Tree::Leaf(2)), Box::new(Tree::Leaf(3)))));
    println!("tree sum {}", sum(&tree));

    let log = Rc::new(RefCell::new(Vec::new()));
    let writer_a = Rc::clone(&log);
    let writer_b = Rc::clone(&log);
    writer_a.borrow_mut().push("a: started");
    writer_b.borrow_mut().push("b: started");
    println!("{:?}, {} owners", log.borrow(), Rc::strong_count(&log));

    let team = Rc::new(Team { name: String::from("Crabs"), members: RefCell::new(Vec::new()) });
    let ferris = Rc::new(Player { name: String::from("Ferris"), team: RefCell::new(Weak::new()), score: Cell::new(0) });
    *ferris.team.borrow_mut() = Rc::downgrade(&team);
    team.members.borrow_mut().push(Rc::clone(&ferris));

    ferris.score.set(ferris.score.get() + 10);
    let team_name = ferris.team.borrow().upgrade().map(|t| t.name.clone());
    println!("{} plays for {:?} with {} points", ferris.name, team_name, ferris.score.get());
    println!("team has {} member(s)", team.members.borrow().len());

    let first = log.borrow();
    println!("second borrow_mut while reading: {}", log.try_borrow_mut().is_err());
    println!("first entry {}", first[0]);
}

Output:

tree sum 6
["a: started", "b: started"], 3 owners
Ferris plays for Some("Crabs") with 10 points
team has 1 member(s)
second borrow_mut while reading: true
first entry a: started

Practice it