What are Smart Pointers and their Types in Rust??

Reading Time: 4 minutes

A pointer is a general concept for a variable that contains an address in memory. The most common kind of pointer in Rust is a reference. Smart pointers, on the other hand, are data structures. Smart pointers implement the traits that are listed below.

  • Deref: used for immutable dereferencing operations, like *v.
  • Drop: used to run some code when a value goes out of scope.

We’ll cover the most common smart pointers in the standard library:

  • Box<T>: for allocating values on the heap.
  • Rc<T>: a reference counting type that enables multiple ownership.
  • RefCell<T>: a type that enforces the borrowing rules at runtime instead of compile time.


Box<T>

The Box smart pointer also called a box, allows you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data. A Box does not have performance overhead, other than storing their data on the heap.

syntax : to interact with values stored within a Box.

fn main() {
    let var = Box::new(5);
    println!("Var = {}", var);
}

Construct function

The Construct function is a data structure used to construct a new pair from its two given arguments. Each item in a cons list contains two elements: the value of the current item and the next item. The last item of the cons list is Nil as Nil does not contain the next item.

enum List {
    Cons(i32, List),
    Nil,
}
use crate::List::{Cons, Nil};
fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}

In the above example, the Rust compiler throws an error has infinite size. The reason is that we’ve defined List with a variant that is recursive: it holds another value of itself directly. Box helps solve the issue of recursive variants or the infinite size problem. Let see how

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

Because a Box<T> is a pointer, Rust always knows how much space a Box<T> needs. As we know that pointers size doesn’t change based on the amount of data it’s pointing to.

This is how cons variant looks like after we use Box in it.


Deref<T>

The Deref Smart Pointer allows users to customize the behavior of dereference operator *. Implementation of Deref<T> allows us to use a smart pointer as a reference.

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

In the above example, we have used Box smart pointer to implement the Deref trait. We define a struct named MyBox and declare a generic parameter T.

Drop<T>

The Drop trait allows you to customize the code that runs when an instance of that struct goes out of scope. It is used to release resources like network connections, files, and used memory.

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

Rust automatically called drop for us when our instances went out of scope.


Rc<T>

The Rc<T> stands for Reference Counted Smart Pointer. This type of Smart Pointer is used to enable multiple ownership of the data which against Rust Ownership principles.

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let first_list = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let second_list = Cons(20, Rc::clone(&first_list));
    let third_list = Cons(24, Rc::clone(&first_list));
}

In the above example, the cons variant consists of data of type i32 and Rc<T> pointing to a list. In main instead of taking ownership of first_list we have, we will clone the Rc<T> list that first_list is holding using Rc::clone.


RefCell<T>

The RefCell<T> represents the single ownership over the data that it holds. The Difference between Box and RefCell is you can have either one mutable reference or any number of immutable references at a given time.

use std::cell::RefCell;  
fn main()  
{  
  let first_var = RefCell::new(15);  
  let second_var = first_var.borrow();  
  let third_var = first_var.borrow();  
  println!("Value of Second Variable is : {}",second_var);  
  println!("Value of Third Variable is : {}",third_var);  
}  

In the above example, we have used the borrow() method. The Borrow method allows us to have multiple immutable borrows at the same time. The borrow_mut() method borrows the mutable value. Mutable borrows can occur once.

use std::cell::RefCell;  
fn main()  
{  
  let first_var = RefCell::new(15);  
  let second_var = var_first.borrow_mut();  
  println!("Now, value of Second Variable is {}",second_var);  
}  

We can combine Rc<T> and RefCell<T> so that we can have multiple owners of mutable data.

  • The Rc<T> allow us to have multiple owners of data but only immutable access to the data.
  • The RefCell<T> lets you mutate the data.
#[derive(Debug)]  
enum List  
{  
 Cons(Rc<RefCell<String>>,Rc<List>),  
 Nil,  
}  
use List:: {Cons,Nil};  
use std::rc::Rc;  
use std::cell::RefCell;  
fn main()  
{  
  let val = Rc::new(RefCell::new(String::from("Java")));  
  let first_list = Rc::new(Cons(Rc::clone(&val),Rc::new(Nil)));  
  let second_list = Cons(Rc::new(RefCell::new(String::from("Rust"))),Rc::clone(&first_list));  
  let third_list = Cons(Rc::new(RefCell::new(String::from("C#"))),Rc::clone(&first_list));
}  

In the above example, we have created an instance of Rc<Refcell<String>> . In the main function, we have created a variable val and store the value “java” to the variable val also, we created a list first_list with a cons variant that holds the value. The variable first_list and val have the ownership of “java” value. After that, we have created the second_list and third_list list and clones the first_list list.

In conclusion, I hope you have gained some information about smart pointers in Rust.

Thanks for reading !!

If you want to read more content like this?  Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.


Knoldus-blog-footer-image

Written by 

I am Software Consultant at Knoldus and I am curious about learning new technologies.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading