Skip to content
Box, Rc, Arc — Smart Pointers
step 1/6

Reading — step 1 of 6

Learn

~2 min readSmart Pointers and Memory

Most Rust values live on the stack. Sometimes you need heap allocation, shared ownership, or self-referential structures. Smart pointers are types that wrap a value and provide additional capabilities.

Box<T> — heap allocation

let boxed: Box<i32> = Box::new(42);
println!("{}", *boxed);    // 42 — dereference to access

Box<T> allocates on the heap and owns the value. Use it for:

  • Recursive types that the compiler can't size (linked lists, trees)
  • Trait objects: Box<dyn Display>
  • Transferring ownership of large values without copying

Classic recursive type:

enum List {
    Cons(i32, Box<List>),    // Box breaks the infinite size
    Nil,
}

Rc<T> — single-threaded shared ownership

When multiple parts of your code need to OWN the same value (like a shared graph node), Rc<T> does reference counting:

use std::rc::Rc;

let a = Rc::new(String::from("hello"));
let b = Rc::clone(&a);
let c = Rc::clone(&a);

println!("{} refs", Rc::strong_count(&a));   // 3
// when all three drop, the String is freed

Rc::clone doesn't copy the value — it bumps the reference count. Each Rc<T> deref'd gives you a &T.

Rc is single-threaded only — sharing across threads requires Arc.

Arc<T> — atomic, thread-safe shared ownership

use std::sync::Arc;
use std::thread;

let data = Arc::new(vec![1, 2, 3]);

let mut handles = vec![];
for _ in 0..3 {
    let d = Arc::clone(&data);
    handles.push(thread::spawn(move || {
        println!("thread sees: {:?}", d);
    }));
}
for h in handles { h.join().unwrap(); }

Atomic ref count → safe to share across threads. Slightly slower than Rc due to atomic operations.

RefCell<T> — interior mutability (single-threaded)

Rust's borrow rules apply at COMPILE time. RefCell<T> moves them to RUNTIME — useful when the compiler can't prove your code is safe but you know it is:

use std::cell::RefCell;

let cell = RefCell::new(5);
*cell.borrow_mut() += 10;
println!("{}", cell.borrow());   // 15

Breaks the borrow rules at compile time but enforces them at runtime — borrow_mut() while another borrow() is active panics.

Common combinations

  • Rc<RefCell<T>> — shared mutable state in single-threaded code (e.g., graph nodes that need updating).
  • Arc<Mutex<T>> — shared mutable state across threads. Mutex is the threaded equivalent of RefCell.

Common mistakes

  • Using Rc across threads — compile error. Use Arc.
  • Creating reference cycles with Rc — leaks memory. Use Weak for one direction.
  • Reaching for smart pointers when borrowing would do — adds complexity for no benefit.
  • Mutating through RefCell from two places — runtime panic. Stick to one mutable borrow at a time.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…