Smart Pointers

Smart Pointer Dereferencing

Smart pointer dereferencing is the mechanism by which a smart pointer wrapper (Box<T>, Rc<T>, Arc<T>, Ref<'_, T>, MutexGuard<'_, T>) provides transparent access to its inner target value T through the unary * operator and automatic deref coercion (Deref and DerefMut).

The Deref and DerefMut Traits

Defined in std::ops, Deref and DerefMut overload the dereference operator *:

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

pub trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

When you write *smart_ptr in an immutable context, the compiler rewrites the expression to *(Deref::deref(&smart_ptr)). In a mutable context (*smart_ptr = new_val), the compiler rewrites it to *(DerefMut::deref_mut(&mut smart_ptr)).

use std::ops::{Deref, DerefMut};

struct TrackedBox<T> {
    value: T,
    writes: usize,
}

impl<T> TrackedBox<T> {
    fn new(value: T) -> Self {
        Self { value, writes: 0 }
    }
}

impl<T> Deref for TrackedBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl<T> DerefMut for TrackedBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.writes += 1;
        &mut self.value
    }
}

fn main() {
    let mut tracked = TrackedBox::new(String::from("initial"));

    // Calls `DerefMut::deref_mut` to mutate the inner String:
    tracked.push_str(" + updated");
    *tracked = tracked.to_uppercase();

    // Calls `Deref::deref` to read the inner String as `&str`:
    let as_str: &str = &tracked;
    println!("value: {:?}, writes: {}", as_str, tracked.writes);
}
value: "INITIAL + UPDATED", writes: 2

Why Smart Pointer Associated Functions Take &Rc<T> Instead of &self

Because Deref automatically forwards .method() calls on a smart pointer to its inner Target type T, adding fn clone(&self) or fn downgrade(&self) as regular self methods on Box<T>, Rc<T>, or Arc<T> could collide with a method of the same name defined on the inner type T.

To prevent method-name collisions with T, smart pointer types define their own operations as associated functions called with path syntax (Rc::clone(&rc), Rc::downgrade(&rc), Rc::strong_count(&rc), Arc::ptr_eq(&a, &b), Box::into_raw(b)).

use std::rc::Rc;

struct Document {
    title: String,
}

impl Document {
    fn strong_count(&self) -> &'static str {
        "Document::strong_count method on inner type"
    }
}

fn main() {
    let doc = Rc::new(Document {
        title: String::from("Spec"),
    });

    // Method syntax resolves via `Deref` to `Document::strong_count`:
    println!("doc.strong_count():     {}", doc.strong_count());

    // Associated function syntax calls `Rc::strong_count`:
    println!("Rc::strong_count(&doc): {} ({})", Rc::strong_count(&doc), doc.title);
}
doc.strong_count():     Document::strong_count method on inner type
Rc::strong_count(&doc): 1 (Spec)

Multi-Layer Smart Pointer Dereferencing

When smart pointers are nested (such as Arc<Mutex<Vec<u8>>>), dereferencing steps through each layer according to its synchronization or borrowing API:

  1. Arc<Mutex<Vec<u8>>> $\xrightarrow{\text{Deref}}$ Mutex<Vec<u8>> (automatic on .lock()).
  2. mutex.lock().unwrap() $\rightarrow$ MutexGuard<'_, Vec<u8>>.
  3. MutexGuard<'_, Vec<u8>> $\xrightarrow{\text{Deref / DerefMut}}$ Vec<u8> $\xrightarrow{\text{Deref}}$ [u8].