~/
Theme

📌 Rust Smart Pointers Explained

Smart pointers in Rust provide additional functionality beyond regular references (&T), such as heap allocation, reference counting, interior mutability, and thread safety.

Box: Heap Allocation and Ownership

What is Box<T>?

  • A smart pointer that allocates data on the heap.
  • Useful for large data structures or recursive types.
  • Has single ownership → Moves on assignment.

How to Use Box<T>

fn main() {
    let x = Box::new(5); // `x` stores the heap-allocated value 5
    println!("x = {}", x);
}

âś… Use Box<T> when:

  • You need to store data on the heap.
  • You want to transfer ownership but still have Rust’s safety.
  • You need indirect recursion (because Rust needs to know the size at compile time).

❌ Don’t use Box<T> when:

  • A simple &T reference is enough.
  • Performance overhead of heap allocation is unnecessary.

Rc: Multiple Owners (Reference Counting)

What is Rc<T>?

  • A reference-counted smart pointer that allows multiple owners of the same data.
  • Tracks how many references exist and deallocates memory when the last reference is dropped.
  • Not thread-safe (use Arc<T> for multi-threading).

How to Use Rc<T>

use std::rc::Rc;

fn main() {
    let a = Rc::new(10);
    let b = Rc::clone(&a); // Increases reference count
    let c = Rc::clone(&a);

    println!("Reference count: {}", Rc::strong_count(&a)); // Prints: 3
}

âś… Use Rc<T> when:

  • You need multiple owners for the same value (e.g., graph/tree structures).
  • You are not working with multiple threads.

❌ Don’t use Rc<T> when:

  • You need thread safety (Rc<T> is not thread-safe → use Arc<T>).
  • You need mutable access (use RefCell<T> instead).

Arc: Thread-Safe Reference Counting

What is Arc<T>?

  • A thread-safe version of Rc<T> that allows shared ownership across threads.
  • Uses atomic reference counting instead of normal reference counting.
  • More expensive than Rc<T> due to thread synchronization overhead.

How to Use Arc<T>

use std::sync::Arc;
use std::thread;

fn main() {
    let a = Arc::new(10);
    let b = Arc::clone(&a);
    
    let handle = thread::spawn(move || {
        println!("Thread value: {}", b);
    });
    
    handle.join().unwrap();
}

âś… Use Arc<T> when:

  • You need multiple owners across threads.
  • You are working in a multi-threaded environment.

❌ Don’t use Arc<T> when:

  • You don’t need thread safety (Rc<T> is more efficient in single-threaded code).
  • You need mutable access (use Mutex<T> with Arc<T> for that).

RefCell: Interior Mutability (Runtime Borrow Checking)

What is RefCell<T>?

  • Allows mutable access to data even when it’s immutable.
  • Unlike Rust’s usual borrowing rules (checked at compile time), RefCell<T> enforces borrowing rules at runtime.
  • Useful when you need shared mutability in a single-threaded program.

How to Use RefCell<T>

use std::cell::RefCell;

fn main() {
    let a = RefCell::new(5);
    *a.borrow_mut() += 10;
    println!("Updated value: {}", a.borrow());
}

âś… Use RefCell<T> when:

  • You need mutable access but Rust’s borrowing rules make it difficult.
  • You are working in a single-threaded environment.

❌ Don’t use RefCell<T> when:

  • You need compile-time borrowing safety (since RefCell<T> allows runtime errors for borrowing violations).
  • You need thread safety (RefCell<T> is not thread-safe → use Mutex<T> instead).

Mutex: Thread-Safe Interior Mutability

What is Mutex<T>?

  • A thread-safe way to enable interior mutability.
  • Ensures that only one thread can access the data at a time (prevents data races).
  • Requires locking/unlocking to access data.

How to Use Mutex<T>

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Final count: {}", *counter.lock().unwrap());
}

âś… Use Mutex<T> when:

  • You need mutable access across multiple threads.
  • You need safe concurrency management.

❌ Don’t use Mutex<T> when:

  • You don’t need thread safety (use RefCell<T> in single-threaded cases).
  • You don’t want the overhead of locks (locks can cause performance bottlenecks).

📌 Summary Table: Choosing the Right Smart Pointer

Smart PointerOwnershipThread-Safe?Mutable Access?Use Case
Box<T>SingleYesNoHeap allocation, recursive types
Rc<T>Multiple (reference counting)NoNoMultiple owners in single-threaded programs
Arc<T>Multiple (atomic reference counting)YesNoMultiple owners in multi-threaded programs
RefCell<T>SingleNoYes (interior mutability)Mutable access in single-threaded programs
Mutex<T>SingleYesYesMutable access in multi-threaded programs