Smart pointers are data structures that perform the same functionalities as a pointer and offer additional features like memory management. Another feature that smart pointers have that pointers don’t have is that they own the data. Smart pointers are used for resource management such as managing file handles and network connection. Both String
and Vector(Vec<T>)
are considered since they have memory and enable the user to manipulate it.
Structs are commonly used to implement smart pointers; however, smart pointers are not like ordinary structs as they offer a Deref and Drop trait.
There are many types of smart pointers:
In the above image the <T>
next every smart pointer denotes the data type, i.e., <T>
denotes the type of data.
Box<T>
In Rust, everything is allocated on stack by default. However, Box<T>
is a smart pointer that provides a reference to the data on the heap rather than the
The visual representation of Box smart pointer is shown below:
fn main(){
let _example = Box::new(200);
}
The construct function is a data structure used to construct a new pair from its two given arguments. These pairs, which consist of pairs, form a list. We can compute the size of recursive type using the Box
#[derive(Debug)]enum List {Cons(i32, Box<List>),Nil,}use List::{Cons, Nil};fn main(){let example = Cons(100,Box::new(Cons(200,Box::new(Nil))));print!("{:?}",example);}
This is one example of how Box
Deref<T>
Deref<T>
is a type of smart pointer that allows a user to manipulate and customize the behavior of the dereferencing operator (*
). It allows the creation of an instance of smart pointer struct that can work with codes that work for both smart pointers and references. The following sample demonstrates a simple implementation of Deref:
use :: std::ops::Deref;struct MyBox<T>{data : T,}impl<T> Deref for MyBox<T>{type Target = T; //declares generic type parameterfn deref(&self) ->&T{&self.data}}fn main(){let example = MyBox{data : 10};print!("{}",*(example.deref()));}
Rc<T>
The reference-counted pointer Rc<T>
, another type of smart pointer, allows multiple users to own or reference the data stored on the heap. It essentially keeps track of how many owners of data are there, i.e., how many times a certain data on heap has been referenced. When
#[derive(Debug)]enum List{Cons(i32, Rc<List>),Nil,}use List::{Cons,Nil};use std::rc::Rc;fn main(){//Rather than taking ownership of List3, we clone Rc<T> list that currently holds List3. This increases the number of references.let list3 = Rc::new(Cons(34, Rc::new(Cons(2,Rc::new(Nil)))));let list2 = Cons(10, Rc::clone(&list3));let list1 = Cons(30,Rc::new(Cons(40, Rc::clone(&list3))));println!("{:?}",list3);println!("{:?}",list2);println!("{:?}",list1);}
RefCell<T>
RefCell<T>
supports the interior mutability pattern, which enables the user to mutate data even when the data is immutable – it allows one mutable or many immutable borrows, but not both at a given instance. Immutable data refers to the data whose state is unmodifiable after its creation (unlike mutable data which can be modified even after its creation).
There is only one owner of data while using this type of smart pointer as, similar to Rc pointer, it does not support multithreading. Unlike Box
The RefCell
borrow()
borrow_mut()
These methods both keep track of runtime borrows (both Ref<T>
and RefMut<T>
) and both (RefRef<T>
goes out of scope. The function definition is:
pub fn borrow(&self)->Ref<T>
pub fn borrow_mut(&self) -> RefMut<T>;
The code below shows different scenarios that are allowed:
use std::cell::RefCell;
fn main()
{
let data = RefCell::new(15);
let mutable_data = data.borrow_mut();
}
let immutable_data_1 = data.borrow();
let immutable_data_2 = data.borrow();
}
let immutable_data_1 = data.borrow();
let mutable_data = data.borrow_mut(); // creates panic.
In order to have multiple owners of mutable data, use Rc
Drop<T>
When a variable goes out of scope, Drop<T>
is used to free the allocated space on the heap. The drop trait helps us customize what happens when a variable goes out of scope. The sample code structure below displays the implementation of the drop trait:
struct SampleSmartPointerImplementation {
data: String,
}
impl Drop for SampleSmartPointerImplementation {
fn drop(&mut self) {
//Add your required sequence of actions here
}
}
However, the drop method needs not be explicitly called because whenever a variable goes out of scope, the method is invoked itself. In Rust, the compiler will automatically execute the specified code when a value goes out of scope.