How to Use the Deref and DerefMut Traits in Rust

Implement Deref and DerefMut traits to make custom smart pointers behave like standard references for reading and mutating data.

When a wrapper needs to act like what it holds

You are building a wrapper around a Vec<u8>. You want to track every read operation for debugging, but you also want to call .push(), .len(), and .iter() without writing a forwarding method for each one. You try calling .push() on your wrapper, and the compiler rejects you. The wrapper exists, but it does not speak the language of the inner type. You could manually forward fifty methods, but that turns your code into a maintenance nightmare. Instead, you teach the compiler how to unwrap your type automatically.

The pointer indirection problem

The Deref and DerefMut traits are Rust's mechanism for smart pointers. They tell the compiler how to translate a wrapper type into a reference to the data it holds. Think of them as automatic adapters. You hold a proprietary power brick, but the wall socket expects a standard plug. The adapter sits between them, translating the shape so the electricity flows without you manually rewiring the house.

Deref handles read access. DerefMut handles write access. Both traits require you to declare a type Target, which is the inner type you want to expose. When you use the * operator on a value that implements Deref, the compiler replaces *value with (*value).deref(). When you use * on a mutable binding, it calls deref_mut(). The real magic happens behind the scenes through deref coercion, which I will cover next.

How deref coercion actually works

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

/// A simple wrapper that holds any type T.
struct MyBox<T>(T);

impl<T> MyBox<T> {
    /// Wraps a value in the box.
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

// Implement Deref to allow reading the inner value.
impl<T> Deref for MyBox<T> {
    type Target = T; // The inner type we expose.

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

// Implement DerefMut to allow modifying the inner value.
impl<T> DerefMut for MyBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0 // Return a mutable reference to the wrapped data.
    }
}

fn main() {
    let mut box_val = MyBox::new(5);

    // The compiler inserts deref() here.
    println!("Read: {}", *box_val);

    // The compiler inserts deref_mut() here.
    *box_val = 10;
    println!("Updated: {}", *box_val);
}

Look at what happens when you write *box_val. You are not manually calling a method. The compiler sees the * operator and checks if the type implements Deref. It finds the trait, calls deref(), and hands you back a &T. The same pattern applies to mutation with DerefMut.

The heavy lifting comes from deref coercion. This is a compile-time feature that automatically converts &T into &U when T implements Deref<Target = U>. The compiler inserts the deref calls for you at function call sites. If a function expects &str, you can pass &String, &Box<String>, or &MyBox<String>. The compiler chains the derefs until it reaches the exact type the function signature demands.

Coercion follows strict rules. It only happens at call sites, never in function definitions or struct fields. It stops as soon as it finds a match. It never converts &mut T to &T automatically, even though Deref is implemented. You must explicitly use &*value if you need to downgrade a mutable reference to an immutable one. The compiler will not guess that you want to drop mutability.

Building a realistic smart pointer

Real code rarely uses MyBox. You will encounter these traits in smart pointers like Box, Rc, Arc, and Ref. Let's build a wrapper that enforces a specific access pattern. A ReadOnlyWrapper implements Deref but deliberately omits DerefMut. This turns any mutable inner type into an immutable view through the wrapper.

use std::ops::Deref;

/// A wrapper that exposes the inner value as immutable, regardless of mutability.
struct ReadOnly<T>(T);

impl<T> ReadOnly<T> {
    /// Creates a new read-only handle.
    fn new(val: T) -> Self {
        ReadOnly(val)
    }
}

// Only Deref is implemented. DerefMut is intentionally missing.
impl<T> Deref for ReadOnly<T> {
    type Target = T;

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

fn print_length(s: &str) {
    println!("Length: {}", s.len());
}

fn main() {
    let data = String::from("hello");
    let wrapped = ReadOnly::new(data);

    // Deref coercion kicks in. &ReadOnly<String> becomes &String, then &str.
    print_length(&wrapped);

    // This fails at compile time. ReadOnly does not implement DerefMut.
    // *wrapped = String::from("world");
}

This pattern appears in concurrent code and API boundaries. You hand out a ReadOnly handle to a worker thread, and the borrow checker guarantees that no one can mutate the data through that handle, even if the original owner still holds a mutable reference elsewhere. The trait implementation acts as a compile-time firewall.

A quick convention note: the Rust community reserves Deref for smart pointers and transparent wrappers. If your type is a domain model like User or DatabaseConnection, do not implement Deref. Implementing it on regular structs breaks encapsulation and creates confusing coercion chains. Keep Deref for types that are fundamentally pointers to other data.

Where the compiler draws the line

Deref coercion is powerful, but it introduces subtle traps. The most common mistake is infinite recursion. If your deref method returns &Self instead of &Self::Target, the compiler enters an infinite loop trying to resolve the type. The compiler catches this and rejects the code with a hard error about recursive type definitions.

Another trap is the "deref storm." When you implement Deref on a domain type, you break encapsulation. The compiler will silently coerce your type into the inner type everywhere, exposing private fields and bypassing your public API. The Rust community treats Deref as a smart pointer contract. If your type is not a pointer, a wrapper, or a reference-counted handle, do not implement it.

You will also run into E0277 (trait bound not satisfied) when coercion fails to bridge a gap. If a function expects &Vec<T> and you pass &MyBox<Vec<T>>, the compiler will not coerce it. Deref coercion stops at the first match. It will convert &MyBox<Vec<T>> to &Vec<T>, but it will not automatically convert &Vec<T> to &[T] in the same step if the signature demands the exact collection type. You must match the signature or use explicit slicing.

Mutable coercion has stricter rules. DerefMut requires &mut self. If you only have an immutable reference to your wrapper, the compiler rejects mutation with E0596 (cannot borrow as mutable). You cannot sneak in a mutation through Deref by casting away constness. The borrow checker tracks the mutability chain from the original binding all the way through the deref calls.

Do not confuse Deref with Clone or Copy. Deref is about access, not ownership. It gives you a reference to the inner data. It does not duplicate the data. If you need a new independent copy, you must implement Clone separately. The compiler will never assume that dereferencing a value should also clone it.

When to reach for Deref and when to skip it

Use Deref when you are building a smart pointer, a reference-counted handle, or a transparent wrapper that should behave exactly like the inner type for read operations. Use DerefMut when the wrapper must allow in-place modification of the inner data without forcing the caller to unwrap it manually. Reach for manual method forwarding when you want to control exactly which methods are exposed, or when the wrapper changes the semantics of the operation. Skip Deref entirely when your type is a domain model, a configuration struct, or a value that should not be silently coerced into another type. Trust the borrow checker. It usually has a point.

Where to go next