Smart Pointers

Box for a heap value, Rc and RefCell when one thread shares, and Arc with Mutex when many threads share.

Smart pointers

Until now one value had one owner, or a borrow. This lesson adds three owners that are still explicit. The thread lesson is where those handles are actually spawned. Learn the pointer first so the spawn has somewhere legal to send it.

PointerOwnersThreads
BoxOneThe owner moves with the box
RcMany, one allocationOne thread. Rc is not Send.
ArcMany, one allocationSafe to send when the inner type is Send and Sync
MutexThe lock, not a second ownerOnly one thread holds the guard. It unlocks in Drop.

Box

Box owns a T on the heap. There is still one owner. Drop frees it. You need it when a type contains itself, because otherwise the size would be infinite: enum List { Cons(i32, Box<List>), Nil }. You also use it to move a large value by moving a pointer, and to hold a trait object, Box<dyn Trait>, which you already saw as a fat pointer.

Boxstack pointerTheap
StatusThe pointer is small

Moving the Box moves one pointer. Deref lets you call T’s methods on the box. *boxed moves or copies the inner value out when the type allows it.

01_box.rsRust
enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn main() {
    let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
    if let List::Cons(head, rest) = list {
        println!("{head}");
        drop(rest);
    }
}

Rc and RefCell

Rc is shared ownership on one thread. Cloning an Rc increments a strong count. It does not clone the inner value. When the last Rc drops, the value drops. Weak is a non-owning pointer so two values can point at each other without a cycle that never frees.

RcownerRc clonesame allocationRefCellborrow checked at run
StatusSeveral owners, one allocation

Rc::clone only copies the pointer and bumps the count. This is still one thread. Rc is not Send, so you cannot move it to another thread.

02_rc_refcell.rsRust
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let shared = Rc::new(RefCell::new(vec![1, 2]));
    let alias = Rc::clone(&shared);
    alias.borrow_mut().push(3);
    println!("{} {}", shared.borrow().len(), Rc::strong_count(&shared));
}

Arc and Mutex

Arc is the same shared owner, safe to send to another thread when the inner type is Send and Sync. A Mutex wraps the value so only one thread holds the lock. lock returns a guard. The guard unlocks in Drop, the same RAII you used for files. If a thread panics while holding the guard, the lock is poisoned. The next lock().unwrap() surfaces that. Prefer not to hold the guard longer than the update.

Arcshared ownerMutexone lockguardunlocks on drop
StatusThe Arc can be cloned and sent

Each thread would get its own Arc clone pointing at one mutex. Sending happens in the next lesson. The type has to be legal before you spawn.

03_arc_mutex.rsRust
use std::sync::{Arc, Mutex};

fn main() {
    let counter = Arc::new(Mutex::new(0));
    {
        let mut guard = counter.lock().unwrap();
        *guard += 1;
    }
    println!("{}", *counter.lock().unwrap());
}