6.7 Smart Pointers and Heap Allocation
Rust offers smart pointers to safely manage heap-allocated data. The examples below are included for completeness, but we will explore all the types of Rust's smart pointers in greater detail in later chapters.
6.7.1 Box<T>
: Heap Allocation
Box<T>
allows you to store data on the heap. Box<T>
implements the Deref
trait, so you can use it similarly to a reference, automatically dereferencing when accessing the underlying data.
fn main() { let b = Box::new(5); // Allocate integer on the heap println!("b = {}", b); }
When b
goes out of scope, the heap memory is automatically freed.
6.7.2 Recursive Types with Box<T>
enum List { Cons(i32, Box<List>), Nil, } fn main() { use List::{Cons, Nil}; let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
Box<T>
allows for types of infinite size by providing a level of indirection.
6.7.3 Rc<T>
and Reference Counting
Rc<T>
enables multiple ownership in single-threaded scenarios through reference counting. Note that Rc<T>
is not safe to use across threads. For multithreaded scenarios, use Arc<T>
instead.
use std::rc::Rc; fn main() { let a = Rc::new(String::from("hello")); let b = Rc::clone(&a); let c = Rc::clone(&a); println!("{}, {}, {}", a, b, c); }
6.7.4 Arc<T>
: Thread-Safe Reference Counting
For multithreaded contexts, Arc<T>
provides atomic reference counting.
use std::sync::Arc; use std::thread; fn main() { let a = Arc::new(String::from("hello")); let a1 = Arc::clone(&a); let handle = thread::spawn(move || { println!("{}", a1); }); println!("{}", a); handle.join().unwrap(); }
6.7.5 RefCell<T>
and Interior Mutability
RefCell<T>
allows for mutable borrows checked at runtime rather than compile time, enabling interior mutability. This is useful in scenarios where you need to modify data but are constrained by the borrowing rules.
use std::cell::RefCell; fn main() { let data = RefCell::new(5); { let mut v = data.borrow_mut(); *v += 1; } println!("{}", data.borrow()); }
Using RefCell<T>
with Rc<T>
You can combine RefCell<T>
with Rc<T>
to have multiple owners of mutable data in single-threaded contexts.
use std::cell::RefCell; use std::rc::Rc; struct Node { value: i32, next: Option<Rc<RefCell<Node>>>, } fn main() { let node1 = Rc::new(RefCell::new(Node { value: 1, next: None })); let node2 = Rc::new(RefCell::new(Node { value: 2, next: Some(Rc::clone(&node1)) })); // Modify node1 through RefCell node1.borrow_mut().value = 10; println!("Node1 value: {}", node1.borrow().value); println!("Node2 next value: {}", node2.borrow().next.as_ref().unwrap().borrow().value); }