Devtechnica

Code, Computers & Beyond.

Quick Reference: Smart Pointers in Rust

4 min read

What is a Smart Pointer?

  • A struct (typically) that behaves like a pointer while offering additional functionality and/or safety guarantees
  • Contains metadata & methods for managing managing underlying data
Some requirements to qualify a struct as a smart pointer.
  • It should contain a pointer to the data it references.
    • This pointer can be a raw pointer (*const T or *mut T) or a smart pointer like Box<T>.
  • It should provide additional functionality or safety guarantees beyond what is provided by ordinary references.
  • It should implement the following traits
    • Deref
    • DerefMut
    • Drop

Some of Rust’s Smart Pointers

Box<T>
  • Allocates memory for T on the heap
  • Holds the pointer to the allocated mem
  • Can give you a sized type for an unsized value.
  • Use cases
    1. Recursive data structures, like linked lists or trees.
    2. Storing large data structures on the heap.
    3. Trait objects, which are useful for polymorphism.
Rc<T>
  • A single-threaded reference-counting pointer
  • Creating a new Rc<T> instance, allocates memory on the heap to store the T value and a reference count
  • The reference count keeps track of the number of Rc instances that point to the same data
  • the data is deallocated from the heap when the reference count reaches 0
  • Usage
    1. clone()
      1. Creates a new Rc<T> instance that points to the same data as the original Rc<T>
    2. downgrade()
      1. Creates a new Weak<T> instance that points to the same data as the original Rc<T> instance
      2. Does not increment the reference count.
      3. This allows for creating a reference cycle, where two or more Rc<T> instances point to each other.
      4. A Weak<T> instance can be upgraded to an Rc<T> instance later if the data is still alive.
  • Uses
    1. Sharing read-only data between multiple parts of a program.
    2. Avoiding unnecessary data cloning in single-threaded applications.
    3. Implementing complex data structures with shared ownership.
Weak<T>
  • Another smart pointer in Rust that is related to Rc<T> and Arc<T> but serves a different purpose
  • Provides a way to create weak references that don’t contribute to the reference count
  • This means that a Weak<T> reference will not prevent the data from being deallocated.
  • Usage To use a Weak<T> reference, you need to upgrade it to a strong reference (Rc<T> or Arc<T>) by calling the upgrade() method. This returns an Option<Rc<T>> or Option<Arc<T>>, which will be Some if the data is still available, and None if the data has been deallocated.
  • Uses
    1. Breaking reference cycles
      1. When using Rc<T> or Arc<T>, it’s possible to create reference cycles, where two or more smart pointers create a cycle of ownership
      2. This can lead to memory leaks because the reference count will never drop to zero, and the memory will not be deallocated
      3. Weak<T> can help break these cycles by providing a way to reference data without contributing to the reference count.
      4. Example
        Consider a parent-child relationship in a tree data structure. Using Rc<T> for both parent and child references could create a cycle. Using a Weak<T> reference for the parent reference in the child node can break this cycle, allowing the memory to be deallocated when it’s no longer needed.
    2. Temporary references without ownership
      1. Sometimes, you might want to have a temporary reference to data managed by an Rc<T> or Arc<T> without taking ownership
      2. Example
        imagine a cache that stores frequently accessed data. You might want to retrieve a reference to the data without increasing the reference count, so the cache can still evict the data when needed.
Arc<T>
  • Arc<T> stands for “Atomic Reference Counted”
  • similar to Rc<T>, but it is thread-safe
  • Uses
    1. Sharing read-only data between multiple threads.
    2. Building concurrent data structures with shared ownership.
RefCell<T>
  • RefCell<T> is a type that provides interior mutability in Rust.
    1. This allows you to mutate a value even if you only have a shared reference to it
  • keeps track of the number of active references to the value it contains
  • borrow() can be used to get an immutable reference to the value
  • borrow_mut() can be used to get a mutable reference to the value
    • if there are any other active references to the value, calling borrow_mut() will panic at runtime.
Cell<T>
  • Another type that provides interior mutability in Rust, but more limited than RefCell<T>.
  • Cell<T> can only be used with values that are Copy
  • use get() to get the value
  • use set() to set the value
Mutex<T>
  • Synchronization primitive for multi-threaded programming
  • Provides exclusive access to a value.
  • When a thread acquires a lock on a Mutex<T>, all other threads that try to acquire the lock will block until the first thread releases it
  • This ensures that only one thread can access the protected value at a time, preventing data races and other concurrency-related bugs
  • Usage
    1. Mutex::lock() is used acquire a lock
      1. it returns a Result<MutexGuard<T>, PoisonError<MutexGuard<T>>>
      2. The MutexGuard<T> is a smart pointer that provides exclusive access to the integer value inside the Mutex<T>
      3. MutexGuard can be dereferenced using the * operator
RwLock<T>
  • Similar to Mutex<T> but allows acquisition of multiple read locks.
  • When a thread acquires a lock on a RwLock<T> for writing, all other threads that try to acquire a lock will block until the writing thread releases it

References