Smart pointers are data structures that act like pointers but with additional behavior managed automatically. Unlike regular references, they own the data they point to and take care of freeing the memory when they go out of scope.
Box<T>
Box<T> allocates data on the heap instead of the stack. It is the most basic smart pointer and is useful for:
Data whose size is unknown at compile time.
Creating recursive data structures.
let x: Box<i32> = Box::new(5);println!("{}", x); // Automatically dereferenced
Note
Box<T> implements the Dereftrait, allowing it to be used like a normal reference.
Use cases
Dynamically sized types
The compiler does not know the size of a recursive enumeration without Box:
// ERROR: infinite sizeenum List { Node(i32, List), End,}// OK: Box has a fixed size (a pointer)enum List { Node(i32, Box<List>), End,}
Avoiding expensive copies
let big_data: Box<Vec<i32>> = Box::new(vec![1, 2, 3, 4, 5]);let reference = &big_data; // Passes a reference, does not copy the vector
Trait objects
Box<dyn Trait> allows returning different types that implement the same trait. See Traits as return type.
fn create(b: bool) -> Box<dyn Greet> { if b { Box::new(Person) } else { Box::new(Dog) }}
Warning
Box<T> frees the memory automatically when it goes out of scope. There is no manual free like in C/C++.
Rc<T>
Rc<T> (Reference Counted) allows multiple owners of the same data on the heap. It counts how many references exist and only frees the data when the last reference is destroyed.
use std::rc::Rc;let a = Rc::new(vec![1, 2, 3]);let b = Rc::clone(&a); // Increments the reference countlet c = Rc::clone(&a); // Increments againprintln!("Count: {}", Rc::strong_count(&a)); // 3
Note
Rc::clone does not create a copy of the data, it only increments the reference counter. It is very cheap.
use std::rc::Rc;let list = Rc::new(vec![1, 2, 3]);let another_list = list.clone(); // Same data, two ownersprintln!("{:?}", list); // OKprintln!("{:?}", another_list); // OK
Warning
Rc<T> only works in a single thread. It cannot be shared between threads. Use Arc<T> for concurrency.
Warning
Rc<T> is immutable by default. You cannot modify the inner data directly. For that, use RefCell<T>.
RefCell<T>
RefCell<T> implements interior mutability: it allows modifying the data even when the reference is immutable. The borrowing check is performed at runtime instead of at compile time.
use std::cell::RefCell;let data = RefCell::new(vec![1, 2, 3]);// Immutable borrow{ let read = data.borrow(); println!("{:?}", read);}// Mutable borrowdata.borrow_mut().push(4);println!("{:?}", data.borrow()); // [1, 2, 3, 4]
Warning
If you try to borrow() while there is an active borrow_mut() (or vice versa), the program will panic at runtime.
Tip
Combining Rc and RefCell allows having multiple owners that can modify data: