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);
}