Loading page…
Rust walkthroughs
Loading page…
Smart pointers in Rust are data structures that act like pointers but also have additional metadata and capabilities. Unlike regular references, smart pointers own the data they point to and can manage resources automatically.
Rust provides several smart pointer types in the standard library:
Box<T> — Heap allocation for single ownershipRc<T> — Reference counting for multiple ownership (single-threaded)Arc<T> — Atomic reference counting for multiple ownership (thread-safe)RefCell<T> — Interior mutability with runtime borrow checkingCell<T> — Interior mutability for Copy typesMutex<T> and RwLock<T> — Interior mutability with locking for concurrencySmart pointers implement the Deref and Drop traits, which allow them to:
Deref)Drop)Understanding when to use each type is crucial for effective Rust programming.
// Box<T> allocates data on the heap
// Useful for recursive types and large data
// Recursive type example: a cons list
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
// Box enables recursive types by providing fixed size
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{:?}", list);
// Box for large data you don't want to copy
let large_data = Box::new([0u8; 1024 * 1024]); // 1MB on heap
// Box for trait objects (dynamic dispatch)
let animal: Box<dyn Animal> = Box::new(Dog);
animal.make_sound();
}
trait Animal {
fn make_sound(&self);
}
struct Dog;
impl Animal for Dog {
fn make_sound(&self) {
println!("Woof!");
}
}use std::rc::Rc;
// Rc<T> enables multiple ownership (single-threaded only)
// Reference counting tracks how many references exist
#[derive(Debug)]
struct Node {
value: i32,
next: Option<Rc<Node>>,
}
fn main() {
// Create a node that can be shared
let a = Rc::new(Node { value: 5, next: None });
println!("Reference count after creating a: {}", Rc::strong_count(&a)); // 1
// Clone the Rc (increments reference count, doesn't copy data)
let b = Rc::clone(&a);
println!("Reference count after cloning to b: {}", Rc::strong_count(&a)); // 2
let c = Rc::clone(&a);
println!("Reference count after cloning to c: {}", Rc::strong_count(&a)); // 3
// When b and c go out of scope, count decreases
// When count reaches 0, data is dropped
// Multiple lists can share the same tail
let shared_tail = Rc::new(Node { value: 10, next: None });
let list1 = Node { value: 1, next: Some(Rc::clone(&shared_tail)) };
let list2 = Node { value: 2, next: Some(Rc::clone(&shared_tail)) };
// Both list1 and list2 point to the same shared_tail
}use std::sync::Arc;
use std::thread;
// Arc<T> is thread-safe reference counting
// Use Arc when sharing data across threads
fn main() {
let data = Arc::new(vec![1, 2, 3, 4, 5]);
println!("Initial reference count: {}", Arc::strong_count(&data));
let mut handles = vec![];
for i in 0..3 {
// Clone the Arc for each thread
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("Thread {}: data = {:?}", i, data_clone);
println!("Thread {}: ref count = {}", i, Arc::strong_count(&data_clone));
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final reference count: {}", Arc::strong_count(&data));
}use std::cell::RefCell;
// RefCell<T> allows mutation through immutable references
// Borrow checking happens at runtime instead of compile time
fn main() {
let data = RefCell::new(5);
// Borrow immutably
let value = *data.borrow();
println!("Current value: {}", value);
// Borrow mutably and modify
*data.borrow_mut() += 10;
println!("Modified value: {}", data.borrow());
// Common pattern: combine with Rc for mutable shared data
let shared_data = RefCell::new(vec![1, 2, 3]);
// Can modify through an immutable reference
shared_data.borrow_mut().push(4);
println!("Shared data: {:?}", shared_data.borrow());
// Runtime panic on invalid borrows
// This would panic at runtime:
// let a = data.borrow_mut();
// let b = data.borrow_mut(); // panic: already borrowed
}use std::rc::Rc;
use std::cell::RefCell;
// Rc<RefCell<T>> is a common pattern for mutable shared data
#[derive(Debug)]
struct Graph {
value: i32,
edges: Vec<Rc<RefCell<Graph>>>,
}
fn main() {
let node1 = Rc::new(RefCell::new(Graph { value: 1, edges: vec![] }));
let node2 = Rc::new(RefCell::new(Graph { value: 2, edges: vec![] }));
let node3 = Rc::new(RefCell::new(Graph { value: 3, edges: vec![] }));
// Create edges (multiple nodes can point to same node)
node1.borrow_mut().edges.push(Rc::clone(&node3));
node2.borrow_mut().edges.push(Rc::clone(&node3));
println!("Node 1: {:?}", node1.borrow().value);
println!("Node 1 edges: {}", node1.borrow().edges.len());
println!("Node 3 is referenced {} times", Rc::strong_count(&node3));
}use std::cell::Cell;
// Cell<T> provides interior mutability for Copy types
// No runtime borrow checking needed
fn main() {
let counter = Cell::new(0);
// Get and set operations
counter.set(counter.get() + 1);
println!("Counter: {}", counter.get());
// Cell works with any Copy type
let flag = Cell::new(false);
flag.set(true);
// Useful for simple state tracking
struct Config {
debug_mode: Cell<bool>,
max_connections: Cell<u32>,
}
let config = Config {
debug_mode: Cell::new(false),
max_connections: Cell::new(10),
};
// Modify without needing &mut
config.debug_mode.set(true);
config.max_connections.set(100);
}use std::sync::{Arc, Mutex};
use std::thread;
// Arc<Mutex<T>> for thread-safe mutable shared state
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = Arc::clone(&counter);
let handle = thread::spawn(move || {
// Lock the mutex to get mutable access
let mut num = counter_clone.lock().unwrap();
*num += 1;
// Lock is automatically released when num goes out of scope
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final counter: {}", *counter.lock().unwrap());
// Protecting complex data
let shared_vec = Arc::new(Mutex::new(vec![]));
{
let mut vec = shared_vec.lock().unwrap();
vec.push(1);
vec.push(2);
}
println!("Shared vector: {:?}", shared_vec.lock().unwrap());
}use std::sync::{Arc, RwLock};
use std::thread;
// RwLock allows multiple readers OR single writer
// Better than Mutex when reads are more frequent than writes
fn main() {
let data = Arc::new(RwLock::new(vec![1, 2, 3]));
// Multiple readers can hold the lock simultaneously
{
let r1 = data.read().unwrap();
let r2 = data.read().unwrap();
println!("Reader 1: {:?}", *r1);
println!("Reader 2: {:?}", *r2);
}
// Writer needs exclusive access
{
let mut w = data.write().unwrap();
w.push(4);
w.push(5);
}
// Spawn reader threads
let mut handles = vec![];
for i in 0..3 {
let data_clone = Arc::clone(&data);
handles.push(thread::spawn(move || {
let reader = data_clone.read().unwrap();
println!("Thread {} read: {:?}", i, *reader);
}));
}
for handle in handles {
handle.join().unwrap();
}
}use std::rc::{Rc, Weak};
use std::cell::RefCell;
// Weak<T> creates non-owning references
// Prevents reference cycles that would cause memory leaks
struct Node {
value: i32,
parent: RefCell<Weak<Node>>, // Weak reference to parent
children: RefCell<Vec<Rc<Node>>>, // Strong references to children
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
// Set parent as weak reference (doesn't increase strong count)
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
// Access parent through weak reference
if let Some(parent) = leaf.parent.borrow().upgrade() {
println!("Leaf parent value: {}", parent.value);
}
println!("Leaf strong count: {}", Rc::strong_count(&leaf));
println!("Branch strong count: {}", Rc::strong_count(&branch));
}| Smart Pointer | Ownership | Thread Safety | Use Case |
|--------------|-----------|---------------|----------|
| Box<T> | Single | Yes | Heap allocation, recursive types, trait objects |
| Rc<T> | Multiple | No (single-threaded) | Shared ownership in single-threaded code |
| Arc<T> | Multiple | Yes | Shared ownership across threads |
| RefCell<T> | Single with interior mutability | No | Runtime borrow checking, mutation through immutable reference |
| Cell<T> | Single with interior mutability | No | Simple Copy types that need mutation |
| Mutex<T> | Single with locking | Yes | Thread-safe mutable state |
| RwLock<T> | Single with locking | Yes | Read-heavy concurrent access |
Key Points:
Box for heap allocation and recursive typesRc/Arc when multiple owners need to share dataRefCell/Cell for interior mutabilityRc<RefCell<T>> for mutable shared data (single-threaded)Arc<Mutex<T>> for mutable shared data (multi-threaded)Weak references to prevent reference cycles