Skip to main content
Overview
Note about Smart Pointers In Rust

Note about Smart Pointers In Rust

January 27, 2025
3 min read

Overview

In Rust, smart pointers are data structures that behave like a pointer, but they also have additional metadata and capabilities. They impose memory safety rules at compile time or runtime, and are responsible for allocation/cleanup via the Drop and Deref traits.


Common Smart Pointers

1. Box<T>

  • Purpose: Allocate values on the heap. Used for:

    • Storing data of unknown size (e.g., recursive types).
    • Transferring ownership without copying data.
    • Creating trait objects (dyn Trait).
  • Ownership: Exclusive (single owner).

Example:

let boxed = Box::new(42); // Stored on the heap

2. Rc<T> (Reference Counting)

  • Purpose: Enable multiple owners for the same data (non-thread-safe).

    • Use when sharing data across multiple parts of a program in single-threaded contexts.
    • Clones increment a reference counter.
  • Ownership: Shared (immutable borrows only).

Example:

use std::rc::Rc;
let rc1 = Rc::new(42);
let rc2 = rc1.clone(); // Both point to the same data

3. Arc<T> (Atomic Reference Counting)

  • Purpose: Thread-safe version of Rc<T>.

    • Uses atomic operations for reference counting (slower than Rc).
    • Use for shared ownership across threads.
  • Ownership: Shared (immutable borrows only).

Example:

use std::sync::Arc;
let arc = Arc::new(42);
let arc_clone = arc.clone(); // Safe to share across threads

4. RefCell<T>

  • Purpose: Enables interior mutability: mutate data behind an immutable reference.

    • Borrowing rules checked at runtime (not compile time).
    • Panics at runtime if rules are violated.
  • Ownership: Single owner, but allows mutable/immutable borrows at runtime.

Example:

use std::cell::RefCell;
let cell = RefCell::new(42);
let mut borrow = cell.borrow_mut();
*borrow += 1;

5. Cell<T>

  • Purpose: Interior mutability for copyable types.
    • No runtime checks (avoids borrow checker by moving/copying values).
    • Works with Copy types (e.g., primitives).

Example:

use std::cell::Cell;
let cell = Cell::new(42);
cell.set(24); // No need for borrow checks

6. Cow<T> (Clone-on-Write)

  • Purpose: Optimize memory by deferring cloning until mutation occurs.

    • Cow::Borrowed holds a reference to data.
    • Cow::Owned holds owned data (allocated on mutation).
  • Use Case: Avoid unnecessary copies for read-heavy data.

Example:

use std::borrow::Cow;
let cow = Cow::Borrowed("hello");
let owned: Cow<_> = cow.to_mut(); // Clones only if mutated

7. Mutex<T> and RwLock<T>

  • Purpose: Thread-safe interior mutability.
    • Mutex: Exclusive locking (one writer at a time).
    • RwLock: Only allows multiple readers or one writer.
    • Used with Arc for shared ownership across threads.

Example:

use std::sync::{Arc, Mutex};
let data = Arc::new(Mutex::new(42));
let mut guard = data.lock().unwrap();
*guard += 1;

Summary Table

PointerPurposeOwnershipThread-Safe?Mutability
Box<T>Heap allocationSingleNoImmutable/Mutable
Rc<T>Shared ownership (single thread)MultipleNoImmutable
Arc<T>Shared ownership (thread-safe)MultipleYesImmutable
RefCell<T>Runtime-checked mutabilitySingleNoMutable
Cell<T>Copy-based mutabilitySingleNoMutable
Cow<T>Clone-on-write optimizationBorrowed/OwnedNoConditional
Mutex<T>Thread-safe exclusive accessSingleYesMutable
RwLock<T>Thread-safe read/write accessSingleYesMutable

Key Takeaways

  • Use Box for heap allocation or trait objects.
  • Prefer Rc/Arc for shared ownership (non-threaded vs. threaded).
  • RefCell/Cell enable controlled mutability where the borrow checker is too restrictive.
  • Cow optimizes for read-heavy, rarely mutated data.
  • Mutex/RwLock handle concurrent access in multithreaded code.