What is the Deref trait

The Deref trait enables custom smart pointers to behave like regular references by defining how to access the inner data.

When a wrapper needs to act like a reference

You build a custom wrapper around a String to track when it was created. You pass the wrapper to a function that expects &str. The compiler rejects you. You add a * to dereference the wrapper, and it compiles. Then you try calling .len() directly on the wrapper without the *, and that works too. You never implemented .len() on your wrapper. Rust is doing something behind the scenes.

That something is the Deref trait. It defines how to turn your type into a reference to something else. More importantly, it powers deref coercion, the mechanism that lets Rust automatically insert dereferences so your wrapper can masquerade as a reference in the right contexts.

The contract: Deref and Target

The Deref trait lives in std::ops. It has one associated type and one method. The associated type, Target, declares what type your wrapper points to. The method, deref, returns a reference to that target.

use std::ops::Deref;

/// A wrapper that holds a value of type T.
struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
    // Target is the type behind the reference.
    type Target = T;

    fn deref(&self) -> &Self::Target {
        // Return a reference to the inner field.
        &self.0
    }
}

fn main() {
    let b = MyBox(5);

    // Explicit dereference calls deref() and then dereferences the result.
    // b.deref() returns &i32. The * turns it into i32.
    println!("{}", *b);
}

The * operator is syntactic sugar. When you write *b, Rust rewrites it to *(b.deref()). The deref method returns a reference, and the outer * dereferences that reference to get the value. This distinction matters. deref gives you a reference. * gives you the value.

Implementing Deref tells the compiler that your type can be treated as a reference to Target. It does not change the type of your value. MyBox<i32> is still MyBox<i32>. It just gains the ability to produce &i32 on demand.

Deref coercion: The compiler's rewrite rule

The real power of Deref is coercion. Rust inserts dereferences automatically in specific contexts. You don't have to write * everywhere. The compiler rewrites your code to call deref when it sees a type mismatch that can be resolved by dereferencing.

Coercion happens in three places:

  1. Type annotations. When you assign a value to a variable with a reference type, Rust coerces the value to match.
  2. Function arguments. When you pass a value to a function expecting a reference, Rust coerces the argument.
  3. Comparisons. When you compare two references, Rust coerces both sides to a common type.
use std::ops::Deref;

/// A wrapper around String.
struct Wrapper(String);

impl Deref for Wrapper {
    type Target = String;

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

/// Expects a reference to str.
fn print_len(s: &str) {
    println!("{}", s.len());
}

fn main() {
    let w = Wrapper(String::from("hello"));

    // Coercion in function argument.
    // Wrapper -> &String -> &str.
    // Rust inserts deref calls to bridge the gap.
    print_len(&w);

    // Coercion in type annotation.
    // Rust coerces &Wrapper to &str.
    let s: &str = &w;
    println!("{}", s);
}

In print_len(&w), Rust sees Wrapper but needs &str. It checks if Wrapper implements Deref. It does, targeting String. Rust checks if String implements Deref. It does, targeting str. Rust chains the derefs. The call becomes print_len(w.deref().deref()). You get &str for free.

Coercion produces references, never values. Rust never inserts a dereference that would move data. If a function expects T, coercion won't help. You must dereference explicitly. This keeps ownership safe. The compiler only coerces when it can guarantee the result is a borrowed reference.

Method resolution and the ambiguity trap

Deref coercion also applies to method calls. When you call w.len(), Rust looks for len on Wrapper. If it doesn't find it, Rust coerces &w to &String and looks for len there. It finds String::len and calls it.

This is why w.len() works even though Wrapper has no len method. The compiler uses coercion to find methods on the inner type.

There is a catch. Method resolution prefers the type you hold. If Wrapper implements len, that version wins. Coercion only kicks in when the method is not found on the wrapper.

use std::ops::Deref;

struct Wrapper(String);

impl Deref for Wrapper {
    type Target = String;

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

impl Wrapper {
    // This shadows String::len.
    fn len(&self) -> usize {
        0
    }
}

fn main() {
    let w = Wrapper(String::from("hello"));

    // Calls Wrapper::len, not String::len.
    // Coercion is skipped because len exists on Wrapper.
    println!("{}", w.len()); // Prints 0
}

If you want the inner method, you must dereference explicitly. (*w).len() forces the compiler to look at String. This rule prevents accidental method hijacking. If your wrapper defines a method, it takes precedence. The inner value stays silent.

Ambiguity can bite you when you have multiple deref layers. If Wrapper derefs to String, and String derefs to str, and both String and str have a method foo, calling w.foo() resolves to String::foo. Rust stops at the first match in the deref chain. It does not search deeper.

Realistic example: Chaining derefs

Standard library types rely on Deref chains. Box<String> derefs to String, which derefs to str. Rc<Vec<i32>> derefs to Vec<i32>, which derefs to [i32]. These chains let you use smart pointers with slice and string APIs without boilerplate.

use std::ops::Deref;
use std::rc::Rc;

/// A wrapper that adds metadata.
struct MetadataString {
    inner: String,
    tags: Vec<String>,
}

impl Deref for MetadataString {
    type Target = String;

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

fn main() {
    let meta = MetadataString {
        inner: String::from("Rust is fast"),
        tags: vec!["systems".to_string()],
    };

    // Chain: MetadataString -> &String -> &str.
    // trim() is defined on str, not String.
    // Rust traverses the chain to find it.
    let trimmed = meta.trim();
    println!("{}", trimmed);
}

The chain works because each step implements Deref. Rust follows the chain until it finds the method or exhausts the types. Deep chains can be confusing. If you have five layers of wrappers, tracking which method resolves where becomes hard. Keep chains shallow. Two or three layers are manageable. Beyond that, explicit dereferences or accessor methods are clearer.

Pitfalls and errors

Deref has traps. Knowing them saves debugging time.

Deref loops. If A derefs to B and B derefs to A, you create a cycle. The compiler catches this and rejects the code with a "deref loop" error. You cannot implement Deref in a way that creates a cycle. The compiler enforces a directed acyclic graph of deref targets.

Mutation via Deref. Deref returns &Target. You cannot mutate the target through Deref. If you try to call a mutating method on a deref-coerced reference, the compiler rejects you with E0596 (cannot borrow as mutable). You need DerefMut for mutation. Deref is read-only.

Shadowing methods. As shown earlier, if your wrapper defines a method with the same name as the inner type, the wrapper wins. This can lead to subtle bugs if you forget the wrapper has the method. Always check the wrapper's methods before assuming coercion will find the inner one.

Coercion context limits. Coercion only happens in specific places. It does not happen in generic type parameters. If you have fn foo<T>(t: T), passing a Wrapper won't coerce it to String. The type parameter T is fixed to Wrapper. You must pass *w or w.0 explicitly. Coercion works for references, not for generic type inference.

Convention aside: The community treats Deref as a marker for smart pointers. Box, Rc, Arc, Ref, and Cell implement Deref. If your type is a domain object like User or Order, do not implement Deref just to access fields. Use explicit getters. Deref leaks implementation details. If you change the inner type, every place relying on deref coercion might break. Explicit methods give you control.

When to use Deref

Use Deref when you are building a smart pointer type that must integrate seamlessly with reference-based APIs. Use Deref when your wrapper is a transparent layer over another type, and you want users to call methods on the inner type without ceremony. Use DerefMut when you need to allow mutation through the wrapper, but only if you can guarantee the safety invariants of the inner type.

Use explicit accessor methods when your wrapper adds behavior, validation, or logging on access. Use explicit methods when you want to hide the inner type completely. Use explicit methods when the wrapper is a domain object, not a pointer.

Reach for standard library types like Box or Rc before writing your own Deref implementation. Reinventing the wheel rarely pays off. The standard library types are optimized and well-tested. Write custom Deref only when you have a specific need that Box or Rc cannot satisfy.

Deref is a bridge, not a disguise. If your type isn't a pointer, don't pretend to be one.

Where to go next