iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Box / Rc / Arc

Smart pointers in Rust wrap data with extra semantics — heap allocation (Box), shared ownership (Rc/Arc), interior mutability (RefCell/Mutex), and weak references. They let you express patterns the borrow checker alone can’t, like graphs and cycles, without unsafe code.

Box, Rc/Arc, RefCell, Mutex, weak

EXAMPLE
// 1) Box<T> — heap allocation, single owner
let b = Box::new(42);
println!("{}", *b);                            // 42 — dereferences automatically

// Use cases:
//   • Recursive types: trees, linked lists
//   • Large values you want on the heap
//   • Trait objects: Box<dyn Trait>

enum List {
    Cons(i32, Box<List>),
    Nil,
}
use List::*;
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));

// 2) Trait objects via Box
trait Shape { fn area(&self) -> f64; }
struct Circle { r: f64 }
struct Square { s: f64 }
impl Shape for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.r * self.r } }
impl Shape for Square { fn area(&self) -> f64 { self.s * self.s } }

let shapes: Vec<Box<dyn Shape>> = vec![
    Box::new(Circle { r: 1.0 }),
    Box::new(Square { s: 2.0 }),
];
for s in &shapes { println!("{}", s.area()); }

// 3) Rc<T> — Reference Counted, SINGLE-THREADED shared ownership
use std::rc::Rc;
let a = Rc::new(String::from("hello"));
let b = Rc::clone(&a);                          // increments count, NOT a deep copy
let c = Rc::clone(&a);
println!("count = {}", Rc::strong_count(&a));  // 3
// Use Rc when many owners want immutable access to the same data within ONE thread.

// 4) Arc<T> — Atomic Rc, THREAD-SAFE shared ownership
use std::sync::Arc;
use std::thread;
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..5).map(|i| {
    let d = Arc::clone(&data);
    thread::spawn(move || println!("thread {i}: {:?}", d))
}).collect();
for h in handles { h.join().unwrap(); }

// 5) RefCell<T> — INTERIOR MUTABILITY, single-threaded
use std::cell::RefCell;
let cell = RefCell::new(5);
*cell.borrow_mut() = 10;                        // mutable borrow at runtime
println!("{}", *cell.borrow());

// Borrow checker rules enforced at RUNTIME:
let b1 = cell.borrow();
// let b2 = cell.borrow_mut();                   // PANICS — already borrowed

// Use RefCell only when:
//   • Compile-time borrow checker is too strict for your pattern
//   • You can prove no aliasing at runtime (e.g. tree traversal)

// 6) Rc<RefCell<T>> — shared mutable state, single-threaded
use std::rc::Rc;
use std::cell::RefCell;
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
let clone = Rc::clone(&shared);
clone.borrow_mut().push(4);
println!("{:?}", shared.borrow());              // [1, 2, 3, 4]

// 7) Arc<Mutex<T>> — shared mutable state, multi-threaded
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
    let c = Arc::clone(&counter);
    handles.push(thread::spawn(move || {
        let mut n = c.lock().unwrap();
        *n += 1;
    }));
}
for h in handles { h.join().unwrap(); }
println!("counter = {}", *counter.lock().unwrap());  // 10

// Other locks:
// RwLock<T> — many readers OR one writer
// AtomicI64, AtomicUsize, etc. — lock-free for primitives
// parking_lot::Mutex — faster alternative to std::sync::Mutex

// 8) Weak<T> — non-owning reference (breaks reference cycles)
use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

let leaf = Rc::new(Node {
    value: 3,
    parent: RefCell::new(Weak::new()),
    children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
    value: 5,
    parent: RefCell::new(Weak::new()),
    children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);   // weak; doesn't bump count

// Weak::upgrade() returns Option<Rc<T>> — None if the value has been dropped.
if let Some(p) = leaf.parent.borrow().upgrade() {
    println!("leaf parent value = {}", p.value);
}

// 9) Cow<T> — copy on write
use std::borrow::Cow;
fn process(input: &str) -> Cow<str> {
    if input.contains(' ') {
        Cow::Owned(input.replace(' ', "_"))     // own + modify
    } else {
        Cow::Borrowed(input)                     // pass through
    }
}
// Avoid allocations unless mutation is needed.

// 10) Pin<T> — for self-referential types and async
use std::pin::Pin;
// Used internally by Future / async fn; rarely written directly.
// Pin prevents the value from moving in memory.

// 11) Choosing the right pointer
//
//   Need              Single thread     Multi thread
//   Sole owner        Box               Box
//   Shared, immut     Rc                 Arc
//   Shared, mut       Rc<RefCell>       Arc<Mutex> / Arc<RwLock>
//   Many readers      Rc                 Arc<RwLock>
//   Weak link         Rc + Weak         Arc + Weak
//   Lock-free atomic  N/A               AtomicXxx (numbers only)

// 12) Performance considerations
// • Box: zero overhead beyond heap alloc; deref is free
// • Rc: one heap alloc per clone of the SAME pointer is cheap; deref is free
// • Arc: atomic counter ops are slightly more expensive than Rc
// • RefCell: runtime borrow check costs ~ns per borrow
// • Mutex: blocking; on contention, schedule switch
// • RwLock: faster reads, slower writes; consider RCU patterns for read-heavy work

// 13) Common bugs
// • Refcell BorrowMutError — multiple borrows; restructure to scope each borrow
// • Cycle leaks with Rc — use Weak for parent references
// • Send + Sync issues — Rc isn't thread-safe; use Arc
// • Mutex poisoning — a thread panicked while holding the lock; recover via err.into_inner()
// • Holding a Mutex across .await — deadlocks in async code; use tokio::sync::Mutex
// • Rc + threads → compile error 'cannot send Rc<...> between threads safely'
// • Box<dyn Trait> vs &dyn Trait — Box owns + heap; &dyn borrows
// • Cloning Arc in a hot path — each clone is an atomic increment; acceptable but measure

Why it matters

Pick the smart pointer that matches your ownership model: Box for heap allocation and trait objects, Rc/Arc for shared ownership, RefCell/Mutex for interior mutability, Weak to break reference cycles. Reach for Arc<Mutex<T>> for shared mutable state across threads — and never hold a std::sync::Mutex across an .await.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
use std::rc::Rc;
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a);
println!("refs = {}", Rc::strong_count(&a));
Try it Yourself »

Discussion

Loading…