🧙Deref Coercion

What is Deref Coercion?

💡 The Deref trait enables "Deref coercion," which allows the compiler to automatically perform implicit dereferencing when calling functions or methods by converting the references of custom types to references of their inner types.

This is how Deref coercion works behind the scenes:

  1. The compiler will check if the argument type implements Deref .

  2. If it does, and the dereferenced type matches the expected type in the function signature, the compiler will automatically inserts calls to the deref method as needed.

When the Deref Coercion is applied?

Rust performs Deref coercion in three specific cases:

CasesHow it works?

From &T to &U when T: Deref<Target=U>

  • This happens when you pass a reference to a type T (e.g., &String) to a function or method that expects a reference to a different type U (e.g., &str).

  • As long as T implements Deref<Target=U>, the compiler will automatically dereference the &T reference to get a reference to the underlying U value.

  • This allows you to use types like String interchangeably with &str in many situations because String implements Deref<Target=str>.

From &mut T to &mut U when T: DerefMut<Target=U>

  • This case is similar to the first one, but it applies to mutable references (&mut T).

  • Deref coercion occurs when you pass a mutable reference to a type T to a function or method that expects a mutable reference to a different type U.

  • As long as T implements DerefMut<Target=U>, the compiler will dereference the &mut T to provide a mutable reference to the underlying U value.

  • This allows you to use types like Box<T> (heap-allocated box) interchangeably with &mut T in some contexts when necessary (assuming Box<T> implements DerefMut<Target=T>).

From &mut T to &U when T: Deref<Target=U>

  • This case is less common, but it's still valid.

  • It allows dereferencing a mutable reference to a type T to get an immutable reference to a type U.

  • Similar to the first case, the condition is that T implements Deref<Target=U>. This can be useful in specific scenarios where you might need a temporary immutable reference from a mutable reference.

Deref with Function & Method Calls

Using the previous example of our custom smart pointer. To demonstrate how the Deref coercion works, we add a new function say_hi which receives a &str reference.

use std::ops::Deref;

#[derive(Debug)]
struct MySmartPointer<T>(T);

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

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

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

fn say_hi(name: &str) {
    println!("Hi, {name}!")
}

fn main() {
    let name = MySmartPointer::new(String::from("Thomas"));

    println!("name = {:?}", name); // Outputs: name = MySmartPointer(5)
    println!("name = {}", *name); // Outputs: name = 5

    say_hi(&name); // Outputs: Hi, Thomas!
}

In this code, the Deref coercion happens in the say_hi function.

The say_hi function expects a &str reference. But we passed &name, which is a reference to a MySmartPointer<String>.

Deref coercion comes into play again:

  • The compiler sees the argument type &MySmartPointer<String> and the expected type &str.

  • MySmartPointer<String> implements Deref<Target=String>, but say_hi needs &str

  • Behind the scenes, the compiler performs a double dereference.

  • First, it dereferences &name to get a reference &String from inner &MySmartPointer<String>.

  • Then, because String implements Deref<Target=str>, it automatically dereferences again to the &String to get the underlying string slice (&str) that say_hi can use.

  • Finally, the say_hi function will get the correct string format and print Hi, Thomas!.

DerefMut for Mutable Dereferencing

Rust also provides the DerefMut trait for mutable dereferencing.

Let’s dive a bit inside the Deref implementation

pub trait DerefMut: Deref<Target = Self::Target> {
  fn deref_mut(&mut self) -> &mut Self::Target;
}

DerefMut trait inherits from the Deref trait, it means it requires everything from Deref and adds its own method.

The different here is the DerefMut has a required method which is called deref_mut. This method takes &mut self (a mutable reference to the implementing type) as an argument and returns a mutable reference (&mut) to the associated type Target.

As the previous example, we have the MySmartPointer smart pointer with implemented Deref trait. In this updates, we will implement the DerefMut trait for MySmartPointer to demonstrate how we can dereference a mutable reference and mutate the data.

use std::ops::{Deref, DerefMut};

#[derive(Debug)]
struct MySmartPointer<T>(T);

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

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

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

impl<T> DerefMut for MySmartPointer<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.0
    }
}

fn say_hi(name: &str) {
    println!("Hi, {name}!")
}

fn main() {
    let mut name = MySmartPointer::new(String::from("Thomas"));
    *name = String::from("Ashley"); // DerefMut happens here

    say_hi(&name); // Outputs: Hi, Ashley!
}

To allow dereference mutable reference for our smart pointer, we’re going to implement DerefMut trait for MySmartPointer. The deref_mut method returns a mutable reference (&mut) to the inner value (&self.0 - the .0 accesses the first value in a tuple struct). This allows dereferencing and mutating to the underlying data.

In the main function, we created a new mutable MySmartPointer with the string “Thomas”.

The DerefMut coercion happens in the name = String::from("Ashley") line.

  • We’re assigning a new String ("Ashley") to the dereferenced value of name.

  • Deref coercion happens because MySmartPointer implements DerefMut<Target=String>.

  • The compiler automatically dereferences name (which is a &mut MySmartPointer<String>) using deref_mut to get a mutable reference (&mut String) to the inner value, and then modifies the inner string through assignment *name.

  • Finally, the say_hi function will print Hi, Ashley!.

Last updated