| name | rust-smart-pointers |
| description | Understand and use Rust smart pointers including Box, Rc, Arc, RefCell, Cell, Cow, and Pin. Use when choosing heap allocation strategies, implementing shared ownership, interior mutability patterns, or working with self-referential types. |
Rust Smart Pointers
Based on The Rust Programming Language Ch. 15, Effective Rust, and the Nomicon.
When to Use This Skill
- Choosing between stack and heap allocation
- Implementing shared ownership (
Rc/Arc)
- Interior mutability (
Cell/RefCell)
- Copy-on-write patterns (
Cow)
- Pinning for self-referential types (
Pin)
- Understanding
Deref coercions
Smart Pointer Overview
| Type | Ownership | Thread-safe | Mutability | Use Case |
|---|
Box<T> | Single | Yes | Via &mut | Heap allocation, recursive types |
Rc<T> | Shared | No | Immutable | Single-thread shared ownership |
Arc<T> | Shared | Yes | Immutable | Multi-thread shared ownership |
Cell<T> | Single | No | Interior | Copy types, no-borrow mutation |
RefCell<T> | Single | No | Interior | Runtime borrow checking |
Mutex<T> | Shared | Yes | Interior | Thread-safe mutable access |
RwLock<T> | Shared | Yes | Interior | Multi-reader, single-writer |
Cow<'a, T> | Borrowed or Owned | Depends | Clone-on-write | Avoid unnecessary copies |
Pin<P> | Same as P | Same as P | Same as P | Prevent moves (async, self-ref) |
Box
Heap allocation with single ownership:
enum List {
Cons(i32, Box<List>),
Nil,
}
let big_array = Box::new([0u8; 1_000_000]);
let drawable: Box<dyn Draw> = Box::new(Circle { radius: 5.0 });
fn process(data: Box<[u8]>) { ... }
Rc (Reference Counted)
Shared ownership, single-threaded:
use std::rc::Rc;
let shared = Rc::new(vec![1, 2, 3]);
let clone1 = Rc::clone(&shared);
let clone2 = Rc::clone(&shared);
assert_eq!(Rc::strong_count(&shared), 3);
Weak References (Break Cycles)
use std::rc::{Rc, Weak};
struct Node {
children: Vec<Rc<Node>>,
parent: Weak<Node>,
}
let parent = Rc::new(Node { ... });
let weak_ref: Weak<Node> = Rc::downgrade(&parent);
if let Some(strong) = weak_ref.upgrade() {
}
Arc (Atomic Reference Counted)
Thread-safe Rc:
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..4).map(|_| {
let data = Arc::clone(&data);
std::thread::spawn(move || {
println!("{:?}", data);
})
}).collect();
Typical pattern: Arc<Mutex<T>> for shared mutable state across threads.
Cell
Interior mutability for Copy types (no borrow tracking):
use std::cell::Cell;
struct Counter {
count: Cell<u32>,
}
impl Counter {
fn increment(&self) {
self.count.set(self.count.get() + 1);
}
}
RefCell
Runtime-checked borrow rules:
use std::cell::RefCell;
let data = RefCell::new(vec![1, 2, 3]);
let borrowed = data.borrow();
println!("{:?}", *borrowed);
drop(borrowed);
let mut mut_borrowed = data.borrow_mut();
mut_borrowed.push(4);
Common pattern: Rc<RefCell<T>> for shared mutable ownership.
Cow<'a, T> (Clone on Write)
Avoids allocation when mutation isn't needed:
use std::borrow::Cow;
fn ensure_lowercase(input: &str) -> Cow<'_, str> {
if input.chars().all(|c| c.is_lowercase()) {
Cow::Borrowed(input)
} else {
Cow::Owned(input.to_lowercase())
}
}
let result = ensure_lowercase("Hello");
println!("{result}");
Pin
Guarantees that the pointed-to value won't be moved in memory:
use std::pin::Pin;
let pinned: Pin<Box<MyFuture>> = Box::pin(MyFuture::new());
let mut val = MyFuture::new();
let pinned = Pin::new(&mut val);
use std::pin::pin;
let pinned = pin!(MyFuture::new());
When Pin Matters
- Async futures (self-referential across
.await points)
- Intrusive data structures (nodes point to themselves)
- Any type that stores pointers to its own fields
Deref Coercions
fn takes_str(s: &str) { ... }
let boxed = Box::new(String::from("hello"));
takes_str(&boxed);
Reference Map
references/box-rc-arc.md — heap allocation, shared ownership, weak references
references/interior-mutability.md — Cell, RefCell, Mutex, patterns
references/pin-cow.md — Pin mechanics, Cow patterns, Deref
Key References