Understanding Smart Pointers in Rust
Published on
When I first learned Rust, smart pointers were extremely confusing. 😬 If you're struggling too, don't worry, I got you.
In this blog post, we're going to talk about smart pointers and interior mutability in detail.
First, What are Pointers in Rust?
A Pointer is a variable that stores memory address which points to some other data in memory. The most common pointer is references (&
) in rust. They don’t have any special capabilities other than referring to data, and have no overhead(additional operational cost).
What are Smart Pointers in Rust and why do we need them?
Smart pointers is basically a way to do more things on top of references(&
). They implement the Deref
and Drop
traits. Moreover, smart pointers can also have ownership of the data they are pointing to.
Deref Trait:- The Deref trait is used to treat smart pointers as a reference, enabling easy access to the data stored behind the smart pointers.
Drop Trait:- The Drop trait lets you decide what to do when a value is going out of scope.
Now that our basics are cleared. I’ll be talking about several types of smart pointers in rust and their usecases.
1. Box<T>
Box is the simplest smart pointer in rust. It is used to dictate that enclosed data needs to be stored on the heap instead of stack.
Usecases:-
- When you need to store data on the heap, use Box.
- When you have a data type whose size is not defined, use Box.
- When you want to create recursive types. Eg- Binary Tree
Usefull Resources:-
2. Rc<T>
(Reference Counting)
Rc comes into the picture when you have multiple references to a memory but you are not sure in which order they are going out of scope. Rc counts the number of references to the memory and it keeps the references alive until the last reference goes out of scope.
Usecases:-
- When you need multiple parts of your code to own and manage access to the same data, use Rc.
- When you don't want to clone data while creating new references of the data, use Rc.
- When your program is single threaded because Rc is not thread-safe.
Useful Links:-
3. Arc<T>
(Atomic Reference Count)
The Arc smart pointer is just like the Rc smart pointer but with extra benefits. It lets you use atomic operations to manage reference count, making it thread-safe.
what are Atomic operations?
Atomic operations ensure that a set of operations on shared data are completed without interruption, providing thread safety and consistency.
Usecases:-
- Arc should be used when you need to share data between multiple threads as it provides thread safety.
Useful Link:-
Note:- It is slower than Rc when dealing with single threads.
4. Refcell<T>
(Reference Cell)
Refcell is used when you want to mutate data even when there are immutable references to that data and this pattern is often referred to as interior mutability
in Rust.
Note:- RefCell itself is not a smart pointer as it holds data instead of referring to it, but it offers two smart pointers, Ref and RefMut, to access the contained data.
How Refcell works?
So basically, Refcell implements runtime borrow checking, rather than compile-time checking similar to Rust’s usual borrowing rules. This allows certain memory-safe scenarios to be executed, which would have been disallowed by the compile-time checks.
Usecases:-
- It should be used when you’re sure your code follows the borrowing rules but the compiler is unable to understand and guarantee that.
- It should be used when you need to manage mutable state in a single-threaded context.
Useful Links:-
5. Mutex<T>
(Mutual Exclusion)
Mutex is helpful when we want to be able to mutate shared data in multiple threads safely. It allows only one thread to access some data at any given time whether it is a writer or reader. It is used with Arc to ensure shared ownership among multi threaded programs.
Note:- Mutex itself is not a smart pointer as it holds data instead of referring to it but the call to lock returns a smart pointer called MutexGuard.
Usecases:-
- Mutex should be used when you need to exclusively access data majorly dealing with high writing operations keeping overhead of locking and unlocking in mind.
Useful Links:-
6. RwLock<T>
(Reader-Writer Lock)
RwLock is similar to Mutex but unlike Mutex it allows multiple threads to read mutable data while only allowing one thread to write to it at a time.
Note:- RwLock itself is not a smart pointer as it holds data instead of referring to it, but it offers two smart pointers, RwLockReadGuard and RwLockWriteGuard, to access the contained data.
Usecases:-
- Rwlock should be used when you have frequent reads and rare write operations.
- When you want to allow multiple readers to access the data concurrently.
Useful Links:-
7. Cow<T>
(Clone on Write)
Cow provides a flexible way of working with borrowed and owned data. It acts as an enum that lets you use either an owned instance of something or a borrowed instance, rather than acting like a smart pointer, using the same code.
Note:- Cow isn't a smart pointer as It doesn't implement Drop
trait. Cows are used to abstract over owned vs references data.
Usecases:-
- It should be used when you want to leverage efficient and smart memory allocation and data handling.
Useful Links:-
8. Cell<T>
Cell is similar to Refcell but it provides zero-cost interior mutability only for Copy types. It's basically a RefCell with less features as you can only take/replace values, but it has the upside of no runtime cost or ability to panic.
Usecases:-
- In cases where mutation is an implementation detail, use Cell.