21.3Deref and deref coercion

Last updated June 13, 2026

Back in lesson 9.7 something quietly magical happened: you passed a &String to a function expecting &str, and it just worked. We named the mechanism, deref coercion, and promised the full explanation once you had smart pointers. You now do. This lesson covers the Deref trait, the thing that lets a smart pointer be transparently used as the value it points to, and the coercion that automatically converts between reference types to spare you a pile of explicit dereferences.

The dereference operator, revisited

You met * back in chapter 9 as the way to reach through a reference to the value behind it: if r is &5, then *r is 5. A Box<T> supports the same operator:

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

    println!("{}", x == 5);      // true: comparing an i32
    println!("{}", *boxed == 5); // true: *boxed reaches the i32 inside the box
}
true
true

*boxed follows the box to the i32 on the heap, exactly as *r follows a reference. This isn't a coincidence or special-casing for Box. The * operator on anything that isn't a plain reference is governed by a trait, and any type that implements it gets to participate.

The Deref trait

The trait is Deref. Implementing it for a type says "here's what * should produce for me," by returning a reference to some inner value. The standard library implements Deref for Box<T> to return &T, which is why *boxed gives you the boxed value. You can implement it yourself; here's a minimal smart pointer that wraps one value, just to see the wiring:

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   // return a reference to the wrapped value
    }
}

fn main() {
    let b = MyBox::new(5);
    println!("{}", *b);
}
5

MyBox is a tuple struct holding one value. Its Deref implementation returns &self.0, a reference to that value. With that in place, *b works. Under the hood, when you write *b Rust actually runs *(b.deref()): it calls deref to get a &T, then applies the real dereference to that reference. The Deref trait is how a type plugs its own meaning into the * operator. (String implements Deref with Target = str, which is the seed of the whole &String-as-&str story.)

Deref coercion: the mystery solved

Here's the payoff. Deref coercion is a convenience the compiler performs: when you pass a reference to a type that implements Deref, and the function wants a reference to the Deref target, the compiler automatically inserts the deref call for you. &MyBox<String> can be passed where &str is expected, and the compiler chains the coercions to get there.

Watch it work with our MyBox:

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

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

Trace what the compiler did with hello(&m). We have &MyBox<String>, and hello wants &str. So the compiler applies deref coercion twice: &MyBox<String> derefs to &String (via MyBox's Deref), and &String derefs to &str (via String's Deref). Two automatic deref calls, inserted at compile time, turn our wrapper into exactly the &str the function needed. Without deref coercion you'd have to write hello(&(*m)[..]), manually dereferencing the MyBox, then the String, then slicing. The coercion makes the ugly explicit form unnecessary.

And that is the chapter-9 mystery, fully explained. When you passed a &String to a &str function back then, deref coercion converted it by calling String's Deref (whose target is str). It was never a special String favor; it's the general Deref coercion rule, and it applies to every smart pointer, including the Box you just learned and any MyBox you write.

Key insight

Deref coercion is why smart pointers feel weightless to use. A Box<String>, an Rc<Vec<i32>>, a plain &String, all of them can be handed to functions written for the inner type, because the compiler silently inserts deref calls until the types line up. You write code in terms of the data you care about (&str, &[T]) and the wrappers get out of the way. The &String-works-as-&str convenience from chapter 9 was your first sighting of a rule that runs through the entire smart-pointer system.

Mutable deref, briefly

There's a mutable cousin, DerefMut, which does the same for &mut: it lets &mut MyBox<T> coerce to &mut T, so you can reach through a smart pointer to modify the inner value. You'll rarely implement it by hand, but it's why *boxed += 1 works on a Box<i32> and why a &mut String can be passed where &mut str operations are needed. The rule mirrors Deref exactly, with mutability threaded through; the coercions still respect chapter 9's borrowing rules (a &mut coercion needs a &mut source).

Quiz time

Question #1

When you write *b for a smart pointer b, what does the compiler actually do?

Show solution

It runs *(b.deref()): it calls the type's Deref::deref method, which returns a reference (&T) to the inner value, then applies the real dereference to that reference. The Deref trait is how a type defines what * means for it. For Box<T>, deref returns &T, so *b yields the boxed value.

Question #2

Explain, in terms of Deref, why you can pass a &String to a function expecting &str.

Show solution

String implements Deref with Target = str, so a &String can be deref-coerced to a &str: the compiler automatically inserts a deref call to convert it when the function expects &str. This is deref coercion, the same general rule that lets any smart pointer (Box, a custom MyBox) coerce to a reference to its inner type. The chapter-9 convenience was this rule's first appearance.

Question #3

For a MyBox<String> and a function hello(name: &str), how many deref coercions does hello(&m) perform, and what are they?

Show solution

Two. &MyBox<String> coerces to &String (via MyBox's Deref, target String), and &String coerces to &str (via String's Deref, target str). The compiler chains them automatically at compile time. Without coercion you'd write the awkward hello(&(*m)[..]) to do both dereferences and the slice by hand.

Deref controls what happens when you reach through a smart pointer. The other defining trait controls what happens when a smart pointer dies. The next lesson (21.4) covers Drop, the cleanup mechanism behind everything that's been freeing itself automatically since chapter 8.