🚀
17. Smart Pointer

Smart Pointer

  • A pointer is a variable that stores the memory address, which points to any other data in the memory.

  • Most common pointer is the reference (only simply borrow the value, they point to, i.e. doesn't have ownership of the value).

  • References doesn't have any special capabilities, i.e. they doesn't have overhead (unlike pointers).

  • Smart Pointers are data structures that act like pointers, but also have additional metadata and capabilities.

  • In many cases, smart pointers have ownership of the data they point to (unlike references, which simply borrow values).

  • Smart pointers are implemented using structs (they implement the deref & drop traits).

  • deref trait allows an instance of the smart pointer struct to behave like a reference, so that you can write code that works with either references or smart pointers.

  • drop trait allows you to customize the code that is run when an instance of the smart pointer goes out of scope.

Box Smart Pointer

fn main() {
    let b = Box::new(5);
 
    println!("b = {}", b);
}
  • Box is a smart pointer, it stores address of the heap memory, where the value 5 is stored.

  • Boxes doesn't have overhead, except storing data in the heap (but they have very limited capabilities)

Usage:

Boxes are useful when you have a type whose size can't be known at compile time and you want to use a value of that type in a context that requires an exact size.

Example:

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

recursive type List has infinite size

2 |     Cons(i32, List), 
  |               ---- recursive without indirection
  • Cons() constructs memory objects which hold two values or pointers to two values.

Fix:

enum List {
    Cons(i32, Box<List>),
    Nil,
}
 
use List::{ Cons, Nil };
 
fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
  • Box instead of calculating the size of the type at compile time, it stores the address of the heap memory, where the value is stored.

Deref Trait

  • deref trait allows an instance of the smart pointer struct to behave like a reference, so that you can write code that works with either references or smart pointers.
fn main() {
    let x = 5;
    let y = &x;
 
    assert_eq!(5, x);
    assert_eq!(5, y);
}
🚫

Error: no implementation for {integer} == &{integer}

fn main() {
    let x = 5;
    let y = &x;
 
    assert_eq!(5, x);
    assert_eq!(5, *y);
}
  • *y dereferences the reference y to get the value it points to.

Deref Trait with Smart Pointers:

fn main() {
    let x = 5;
    let y = Box::new(x);
 
    assert_eq!(5, x);
    assert_eq!(5, *y);
}
  • Box smart pointer implements the deref trait, which allows it to be dereferenced like a reference.

  • Here *y dereferences the Box smart pointer, to get the value from the address storing the duplicate value stored in heap.

Custom Smart Pointer

  • Implementing the Deref trait on your own data structures allows you to use all the methods provided by the standard library.
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
    }
}
 
fn main() {
    let x = 5;
    let y = MyBox::new(x);
 
    assert_eq!(5, x);
    assert_eq!(5, *y);
}

Implicit Deref Coercions

  • Implicit Deref Coercion is a convenience that Rust performs on arguments to functions and methods which implement the Deref trait.

  • It is used to convert a reference of one type into a reference of another type.

fn main() {
    let m = MyBox::new(String::from("Rust"));
 
    hello(&m);
    hello(&(*m)[..]);
}
 
fn hello(name: &str) {
    println!("Hello, {}!", name);
}
  • Implicit Deref Coercion: &MyBox<String> -> &String -> &str

  • &(*m) dereferences the MyBox<String>, then & creates a reference to the String, and [..] gets a slice of String.

⚠️

Rust cannot perform deref coercion:

From &T(immutable reference) to &mut U (mutable reference)

Drop Trait

  • drop trait allows you to customize the code that is run when an instance of the smart pointer goes out of scope.

  • It automatically runs when the value goes out of scope.

struct CustomSmartPointer {
    data: String,
}
 
impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}
 
fn main() {
    let c = CustomSmartPointer { data: String::from("my stuff") };
    let d = CustomSmartPointer { data: String::from("other stuff") };
    println!("CustomSmartPointers created.");
    drop(d);
    println!("CustomSmartPointer dropped before the end of main.");
}
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
CustomSmartPointer dropped before the end of main.
Dropping CustomSmartPointer with data `my stuff`!

© 2024 Driptanil Datta.All rights reserved

Made with Love ❤️

Last updated on Mon Oct 20 2025