Treating Smart Pointers Like Regular References with Deref

The Deref trait customizes the dereference operator (*) behavior, allowing smart pointers to behave like regular references.

Basic Dereferencing

fn main() {
    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

Using Box<T> Like a Reference

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

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

Box<T> can be dereferenced like a regular reference due to its Deref implementation.

Implementing Custom Smart Pointers

struct MyBox<T>(T);

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

fn main() {}

This MyBox<T> type won’t work with the dereference operator without implementing Deref:

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);
    assert_eq!(5, *y);
}

Implementing the Deref Trait

use std::ops::Deref;

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

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

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);
    assert_eq!(5, *y);
}

The deref method returns a reference to the inner data. When you write *y, Rust actually executes *(y.deref()).

Deref Coercion

Deref coercion automatically converts references to types implementing Deref into references to other types. This enables seamless interaction between different smart pointer types.

fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {}

Calling a function expecting &str with &MyBox<String>:

use std::ops::Deref;

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

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

struct MyBox<T>(T);

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

fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}

Rust automatically applies deref coercion: &MyBox<String>&String&str.

Without deref coercion, you’d need explicit conversions:

use std::ops::Deref;

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

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

struct MyBox<T>(T);

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

fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&(*m)[..]);
}

Deref Coercion Rules

Rust performs deref coercion in three cases:

  1. &T to &U when T: Deref<Target=U>
  2. &mut T to &mut U when T: DerefMut<Target=U>
  3. &mut T to &U when T: Deref<Target=U>

Note: Immutable references cannot coerce to mutable references due to borrowing rules.