Smart Pointers
Box puts values on the heap, Rc/Arc share ownership, and RefCell allows interior mutability — all still checked by the compiler.
Box<T> — Heap Allocation
Box moves a value to the heap but keeps a single owner on the stack. Use it when a value is too large for the stack, when its size is unknown at compile time (recursive types), or when you must transfer ownership without copying:
Figure 1 — the stack holds a pointer; the value lives on the heap and is freed when the Box drops.
fn main() {
let b = Box::new(5); // 5 lives on the heap now
println!("boxed = {b}"); // derefs to 5 automatically
// recursive type needs a Box: the size of Node would otherwise be infinite
enum List {
Cons(i32, Box),
Nil,
}
use List::*;
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
let _ = list;
}
Deref & Drop
Smart pointers behave like references thanks to Deref, and clean up via Drop. You rarely call these traits by name — the compiler and runtime do:
use std::ops::Deref;
struct MyBox(T);
impl MyBox {
fn new(x: T) -> Self { MyBox(x) }
}
impl Deref for MyBox {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
impl Drop for MyBox {
fn drop(&mut self) { /* cleanup runs automatically at scope end */ }
}
fn main() {
let x = MyBox::new(String::from("rust"));
// Deref lets method calls coerce like a plain &String
println!("len = {}", x.len());
}
Rc & Arc — Shared Ownership
Ownership normally forbids two owners, but Rc/Arc count references so a value can have several owners at once. Rc is single-threaded; Arc uses atomics and is thread-safe:
Figure 2 — the value lives until the last clone drops and the count reaches zero.
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a); // increments count to 2, no data copy
println!("owners of a = {}", Rc::strong_count(&a));
println!("{a} / {b}"); // both see the same value
}
RefCell — Interior Mutability
RefCell moves the borrow rules from compile time to run time: it lets you mutate through a shared reference, then panics (rather than corrupting memory) if you break the read/write rule at runtime:
use std::cell::RefCell;
fn main() {
let counter = RefCell::new(0);
// mutate through a shared (&) reference
*counter.borrow_mut() += 1;
*counter.borrow_mut() += 1;
println!("count = {}", counter.borrow());
}
Rc<RefCell<T>> pattern — shared ownership plus interior mutability — and watch a double borrow_mut() panic at runtime.