Smart Pointers
Why Smart Pointers Exist
A smart pointer is a data structure that acts like a pointer while storing additional metadata, ownership invariants, and automatic resource cleanup logic. Unlike plain references (&T and &mut T), which only borrow data owned elsewhere and carry compile-time lifetimes, smart pointers typically own the data they point to and manage its lifetime through the Deref and Drop traits.
Limitations of Plain References and Stack Values
Plain references and stack-allocated values solve most memory management needs, but several scenarios require capabilities beyond lexical borrowing and fixed stack frames:
- Dynamic heap lifetime: A value must outlive the stack frame that created it without copying a large payload up the call stack.
- Indirection for recursive or unsized types: Recursive types (
enum Tree { Node(T, Tree, Tree) }) and trait objects (dyn Trait) have sizes that are either infinite or unknown at compile time unless placed behind a fixed-size pointer. - Shared ownership: Multiple independent parts of a program need to keep the same heap allocation alive until the last user finishes with it (
Rc<T>,Arc<T>). - Runtime-checked mutation: A graph, cache, or observer pattern requires mutating an inner field through a shared reference (
Cell<T>,RefCell<T>,Mutex<T>,RwLock<T>). - RAII resource guards: A lock, file handle, or memory buffer must be released deterministically when the handle goes out of scope.
Plain Reference (&T) Smart Pointer (Box<T> / Rc<T> / Arc<T>)
+-------------------+ +-------------------+
| ptr (borrows T) --+---+ | ptr (owns heap T)-+---> +---------------+
+-------------------+ | | metadata / counts | | Heap Value T |
v +-------------------+ | (freed when |
Stack Owner of T | smart ptr |
(must outlive &T) | is dropped) |
+---------------+
Core Traits That Define a Smart Pointer
In Rust, smart pointers are structs that implement one or both of two standard library traits:
| Trait | Method | Purpose |
|---|---|---|
std::ops::Deref (DerefMut) |
fn deref(&self) -> &Self::Target |
Allows *ptr dereferencing and automatic .method() resolution on the inner value |
std::ops::Drop |
fn drop(&mut self) |
Runs deterministic cleanup (freeing heap memory, decrementing reference counts, or releasing locks) when the smart pointer leaves scope |
Overview of Standard Library Smart Pointers
| Type | Ownership Model | Mutation Model | Thread Safety |
|---|---|---|---|
Box<T> |
Single owner on heap | Immutable (&Box<T>) or mutable (&mut Box<T>) |
Send + Sync (if T: Send + Sync) |
Rc<T> |
Shared reference count | Immutable shared access (&T) |
Single-threaded (!Send, !Sync) |
Arc<T> |
Atomic reference count | Immutable shared access (&T) |
Thread-safe (Send + Sync if T: Send + Sync) |
Weak<T> |
Non-owning weak reference | Must upgrade to Option<Rc<T>> / Option<Arc<T>> |
Matches rc::Weak or sync::Weak |
Cow<'a, B> |
Borrowed or owned on write | Clones into owned data on first to_mut() |
Send + Sync (if B and B::Owned are) |
MutexGuard<'a, T> |
Scoped lock guard | Exclusive &mut T while guard lives |
!Send, Sync (if T: Sync) |
use std::ops::Deref;
use std::rc::Rc;
fn print_length(text: &str) {
println!("length of {:?} = {}", text, text.len());
}
fn main() {
let boxed: Box<String> = Box::new(String::from("heap owned"));
let shared: Rc<String> = Rc::new(String::from("reference counted"));
// Both `Box<String>` and `Rc<String>` deref-coerce to `&str`:
print_length(&boxed);
print_length(&shared);
println!("explicit deref: {}", boxed.deref());
}
length of "heap owned" = 10
length of "reference counted" = 17
explicit deref: heap owned