Smart pointers in Rust

Sep 30, 2021

Usually a pointer is a variable that points to address of another variable while smart pointers have more capabilities. Rust smart pointers owns data they reference to while normal pointers borrows data they point to. Examples of smart pointers in rust include String,vec<_> and etc… All smart pointers implements Deref and Drop traits. Deref trait allows a smart pointer to behave like a reference pointer so you can write code that works with either references or smart pointers. Drop trait allows to write code that run when an instance of the smart pointer goes out of scope.

Box<T> is a smart pointer that allows you to store data on heap rather than stack. Box<T> is used when size can’t be known at compile time and what to use exact value, when you have huge data but don’t what to transfer ownership when you do so and when you want to own a value but you only care that the type it implements a partcular trait rather than a specific type.

A type whose size can’t be know at compile time is called a Recusive type. Rust can’t know exactly the a size of a recursive type but knows that of a box so you opt in your recursive type in a box and boom you have enabled recursive type.

Building our own smart pointer

The example code below expresses a minimal implementation of Box smart pointer

use std::ops::Deref;

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

    fn deref(&self) -> &Self::Target {
      // we did this so deref returns the value with want to access with *
      &self.0
    }
}

// Drop trait is run before a variable is cleaned by rust compiler
impl<T> Drop for MyBox<T> {
   fn drop(&mut self){
   // You can run more code here
      println!("Dropping value");
   }
}

struct MyBox<T>(T);

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

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, x);
    // the compiler now knows the value to return since it look up for .deref() method
    // meaning that *y becomes *(y.deref()) after compilation
    assert_eq!(5, *y);
}

Deref coersion

This is a mechanism that rust uses to convert a type into a reference of another type like &String to &str. It does so because String implements Deref trait and such it returns &str. Deref coesion happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match parameter type. To implement Deref trait on mutable type you use DerefMut.

Enabling multiple ownership

To enable mutiple ownership Rust has a type called reference counting Rec<T>. It keeps track of of the number of references a vaue has to determine if it is still in use. If no reference the value can be cleaned without any reference becoming invalid. Rec<T> is used only in single threaded scnerios.

Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to that data. This pattern is normal prohibited by borrowing rules. RefCell<T> follows this pattern and inforce the borrowing rules at runtime rather than being at compile time. The common use case of RefCell<T> is in combination with Rec<T>, once you do so you get a value that can have multiple owners and can be mutable.

When working with relationship from parent to child using Weak<T>, you are able to have children point to parent and vice versa without creating memory leaks.