Recursive types, shared graphs and trees, and trait objects all need smart pointers beyond plain references.
A smart pointer is a struct that owns data and acts like a pointer by implementing Deref (so *p and method calls reach the inner value) and Drop (cleanup when it goes away). String and Vec are smart pointers too.
`Box<T>` stores a T on the heap with a single owner. Use it for:
- recursive types, whose size would otherwise be infinite:
enum List { Cons(i32, Box<List>), Nil }, - trait objects:
Box<dyn Error>,Box<dyn Fn()>, - moving a large value around cheaply by moving only the pointer.
`Rc<T>` (reference counted) allows multiple owners of the same value in single-threaded code. Rc::clone(&rc) increments a counter instead of copying the data, and the value is dropped when the last Rc goes away. Rc::strong_count shows the count. Data inside an Rc is read-only, because shared ownership implies shared references.
`RefCell<T>` moves the borrowing rules from compile time to runtime. cell.borrow() returns a guard acting like &T, cell.borrow_mut() one acting like &mut T, and violating "many readers or one writer" panics instead of failing to compile. try_borrow_mut returns a Result instead.
Combined, `Rc<RefCell<T>>` gives shared, mutable data: a common shape for graphs, observers and GUI state. Use it sparingly; it is a sign that ownership is genuinely shared, not a way around thinking about ownership.
Two reference-counted values pointing at each other never reach a count of zero, which leaks memory. Break cycles with `Weak<T>` (Rc::downgrade), a non-owning pointer you upgrade() to an Option<Rc<T>>, typically for parent links in trees.
Rc and RefCell are not thread-safe; their multithreaded counterparts are Arc and Mutex.
Example
use std::cell::RefCell;
use std::rc::{Rc, Weak};
enum List { Cons(i32, Box<List>), Nil } // Box gives the recursive type a known size
fn sum(list: &List) -> i32 {
match list { List::Cons(v, rest) => v + sum(rest), List::Nil => 0 }
}
#[derive(Default)]
struct Node {
name: String,
parent: RefCell<Weak<Node>>, // non-owning back link
children: RefCell<Vec<Rc<Node>>>, // owning links
}
fn main() {
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
println!("sum = {}", sum(&list)); // sum = 3
let root = Rc::new(Node { name: "root".into(), ..Default::default() });
let leaf = Rc::new(Node { name: "leaf".into(), ..Default::default() });
root.children.borrow_mut().push(Rc::clone(&leaf));
*leaf.parent.borrow_mut() = Rc::downgrade(&root);
let parent = leaf.parent.borrow().upgrade().map(|p| p.name.clone());
println!("{parent:?}, leaf strong = {}", Rc::strong_count(&leaf)); // Some("root"), leaf strong = 2
}Remember
Box<T>: single owner on the heap, for recursive types and trait objectsRc<T>: shared ownership via reference counting, read-only, single threadRefCell<T>: borrow rules checked at runtime (panics on violation); breakRccycles withWeak
Watch out for
- Holding a
borrow()guard while calling code that doesborrow_mut()on the same cell, which panics.