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
- Recursive data structures, like linked lists or trees.
- Storing large data structures on the heap.
- 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
- clone()
- Creates a new Rc<T> instance that points to the same data as the original Rc<T>
- downgrade()
- Creates a new Weak<T> instance that points to the same data as the original Rc<T> instance
- Does not increment the reference count.
- This allows for creating a reference cycle, where two or more Rc<T> instances point to each other.
- A Weak<T> instance can be upgraded to an Rc<T> instance later if the data is still alive.
- clone()
- Uses
- Sharing read-only data between multiple parts of a program.
- Avoiding unnecessary data cloning in single-threaded applications.
- 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
- Breaking reference cycles
- 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
- This can lead to memory leaks because the reference count will never drop to zero, and the memory will not be deallocated
- Weak<T> can help break these cycles by providing a way to reference data without contributing to the reference count.
- 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.
- Temporary references without ownership
- Sometimes, you might want to have a temporary reference to data managed by an Rc<T> or Arc<T> without taking ownership
- 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.
- Breaking reference cycles
Arc<T>
- Arc<T> stands for “Atomic Reference Counted”
- similar to Rc<T>, but it is thread-safe
- Uses
- Sharing read-only data between multiple threads.
- Building concurrent data structures with shared ownership.
RefCell<T>
- RefCell<T> is a type that provides interior mutability in Rust.
- 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
- Mutex::lock() is used acquire a lock
- it returns a Result<MutexGuard<T>, PoisonError<MutexGuard<T>>>
- The MutexGuard<T> is a smart pointer that provides exclusive access to the integer value inside the Mutex<T>
- MutexGuard can be dereferenced using the * operator
- Mutex::lock() is used acquire a lock
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