Deref trait

In the previous exercise you didn't have to do much, did you?

Changing

#![allow(unused)]
fn main() {
impl Ticket {
    pub fn title(&self) -> &String {
        &self.title
    }
}
}

to

#![allow(unused)]
fn main() {
impl Ticket {
    pub fn title(&self) -> &str {
        &self.title
    }
}
}

was all you needed to do to get the code to compile and the tests to pass. Some alarm bells should be ringing in your head though.

It shouldn't work, but it does

Let's review the facts:

  • self.title is a String
  • &self.title is, therefore, a &String
  • The output of the (modified) title method is &str

You would expect a compiler error, wouldn't you? Expected &String, found &str or something similar. Instead, it just works. Why?

Deref to the rescue

The Deref trait is the mechanism behind the language feature known as deref coercion.
The trait is defined in the standard library, in the std::ops module:

#![allow(unused)]
fn main() {
// I've slightly simplified the definition for now.
// We'll see the full definition later on.
pub trait Deref {
    type Target;
    
    fn deref(&self) -> &Self::Target;
}
}

type Target is an associated type.
It's a placeholder for a concrete type that must be specified when the trait is implemented.

Deref coercion

By implementing Deref<Target = U> for a type T you're telling the compiler that &T and &U are somewhat interchangeable.
In particular, you get the following behavior:

  • References to T are implicitly converted into references to U (i.e. &T becomes &U)
  • You can call on &T all the methods defined on U that take &self as input.

There is one more thing around the dereference operator, *, but we don't need it yet (see std's docs if you're curious).

String implements Deref

String implements Deref with Target = str:

#![allow(unused)]
fn main() {
impl Deref for String {
    type Target = str;
    
    fn deref(&self) -> &str {
        // [...]
    }
}
}

Thanks to this implementation and deref coercion, a &String is automatically converted into a &str when needed.

Don't abuse deref coercion

Deref coercion is a powerful feature, but it can lead to confusion.
Automatically converting types can make the code harder to read and understand. If a method with the same name is defined on both T and U, which one will be called?

We'll examine later in the course the "safest" use cases for deref coercion: smart pointers.

Exercise

The exercise for this section is located in 04_traits/07_deref