Smart Pointers

  • These are custom reference types that can store additional data.
  • Any custom type that implements Deref and Drop is a smart pointer.
  • Rust walks the tree of potential types that implement Deref to allow for automatic dereferencing/deref coercion to a given target type:
    fn hello(name: &str) {
      println!("Hello, {}!", name);
    }
    
    fn main() {
      // Box<T> implements `Deref` and returns a `&T` (in this case a `&String`)
      // ... which in turn implements `Deref` and returns a `&str`
      // ... which `hello` accepts
      let m = Box::new(String::from("Rust"));
      hello(&m);
    }
    
  • Not sure what Rust does if there are multiple paths to the target reference type, though.
  • The Rust compiler knows about three builtin smart pointer types.

Box<T>

  • A container pointing to data on the heap
  • The box owns the data on the heap, and allows mutable/immutable borrows.
  • Different from a regular reference in that it allows boxing primitives.
  • And allows for indirection in recursive type definitions:
    // Fails because this definition recurses indefinitely
    // when Rust attempts to figure out how much memory 
    // a `List<T>` needs.
    enum List<T> {
      Cons(T, List<T>),
      Nil
    }
    
    // This works
    enum List<T> {
      Cons(T, Box<List<T>>),
      Nil
    }
    

Rc<T> (Reference Counted Pointer)

  • Use this if you want multiple owners for something (and having one owner + immutable references is untenable).
  • This is a reference counting container, (effectively) allowing multiple owners for a single allocation.
  • Uses its Drop trait to deallocate when it’s reference count drops to 0.
  • Supports weak references via downgrade.
  • Use Rc::clone to obtain a new (owning) reference to the allocation.
  • This pattern is vulnerable to memory leaks.
  • An Rc<T> derefs to an immutable reference to the underlying data, but can provide a mutable reference (assuming it’s the only reference present) too via get_mut.
  • Arc<T> is a thread-safe version, and can be used with Mutex to allow multiple threads to own a piece of data:
    // We're going to share this empty vector across threaeds
    let data = Arc::new(Mutex::new(vec![]));
    
    // Start 16 threads, give each one ownership of the mutex
    // guarding the vector by cloning the `Arc`. Each thread
    // then calls `lock()`, which only unlocks for one caller
    // at a time, and returns a mutable reference to the vector 
    // held by the mutex. The mutex is unlocked when `v` goes
    // out of scope.
    let threads: Vec<_> = (0..16).map(|i| {
      let data = Arc::clone(&data);
      thread::spawn(move || {
        let mut v = data.lock().unwrap();
        v.push(i);
      })
    }).collect();
    
      
    // Wait until all 16 threads are done.
    for t in threads {
        t.join().unwrap();
    }
    
    // Mutex { data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] } 
    println!("{:?}", data);
    

RefCell<T>

  • Use this if you want to mutate something (non-public scratch space, for example) behind an immutable reference.
  • Immutable container to a mutable piece of the heap.
  • Single owner, allows any number of borrows, but unlike regular allocations, the borrow rules are enforced at runtime, and rust panics if there’s a violation.
  • Deallocates the data on the heap when the owner goes out of scope.
  • Doesn’t implement Deref (because this is a Ref Cell, not a reference); use borrow/borrow_mut instead.

References

Edit