Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15.3 Implicit Deref Coercion and Mutability

15.3.1 Implicit Deref Coercion for Functions and Methods

Implicit deref coercion is a convenience feature for functions and methods.

Its principle is this: *if type T implements the Deref trait, then deref coercion can convert a reference to T into the reference produced after applying Deref to T.

When a reference of some type is passed to a function or method and its type does not match the declared parameter type, deref coercion happens automatically. The compiler makes a series of calls to deref to convert it to the required parameter type. This happens at compile time, so there is no additional performance overhead.

That sounds a bit abstract, so let’s look at an example. We will continue from the code in the previous article:

#![allow(unused)]
fn main() {
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
    }
}
}

This is the code from the previous article. It defines the MyBox tuple struct (see 5.1. Defining and Instantiating Structs for an introduction to tuple structs), creates the new function, and implements the Deref trait for it, so we can use ordinary dereference operations on MyBox.

Here is the additional code:

#![allow(unused)]
fn main() {
fn hello(name: &str) {
    println!("Hello, {}", name);
}
}

The hello function takes &str, that is, a string slice, and prints it.

Now let’s look at the main function:

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

m is of type MyBox<String>, and &m is &MyBox<String>. However, hello expects &str, and this code does not cause an error. Why?

First, MyBox already implements the Deref trait, so Rust can call deref to convert &MyBox<String> into &String. That is the somewhat abstract rule we just discussed.

That is still not the end of the conversion. &String and &str are different types, so how does that conversion happen? Because String also implements the Deref trait, and its deref implementation returns a string slice of type &str, Rust uses deref on &String to convert &String into &str. The type finally matches.

If Rust did not have deref coercion, the code would look like this:

#![allow(unused)]
fn main() {
hello(&(*m)[..]);
}
  • First, use the dereference operator * to convert m from MyBox<String> into String.
  • Then add the reference symbol & to convert String into &String.
  • By using the slice syntax [..], you can get a reference to the full contents of the String and convert its value from &String into &str.

15.3.2 Deref and Mutability

You can use the DerefMut trait to overload the * operator for mutable references. Compared with Deref, DerefMut adds Mut, which means that DerefMut returns a mutable reference &mut T, whereas Deref returns an immutable reference &T.

When a type and trait satisfy the following three cases, Rust performs deref coercion:

  • When T: Deref<Target = U>, &T can be converted to &U: T implements the Deref trait, and the return type of deref under Deref is &U, so &T can be converted to &U. For example, the MyBox type in the code above implements Deref, and its deref method returns &T, so &MyBox can be converted to &T.

  • When T: DerefMut<Target = U>, &mut T can be converted to &mut U. T implements the DerefMut trait (DerefMut returns a mutable reference &mut T), and the return type of deref under DerefMut is &mut U, so &mut T can be converted to &mut U.

  • When T: Deref<Target = U>, &mut T can be converted to &U. Rust can automatically convert a mutable reference into an immutable reference, but the reverse is definitely not allowed. Converting an immutable reference into a mutable reference requires the reference to be unique (this was discussed in the borrow rules, see 4.4. Reference and Borrowing).