What Deref and DerefMut actually do

Deref allows a type to behave like a shared reference to another type. DerefMut does the same for mutable references. The compiler can automatically insert deref coercions in many contexts, such as:

  • calling methods on the inner type
  • passing &MyType where &InnerType is expected
  • passing &mut MyType where &mut InnerType is expected

This is why Box<T>, Rc<T>, Arc<T>, String, and Vec<T> feel ergonomic in Rust. They are not “just” containers; they are carefully designed wrappers with well-understood deref behavior.

A minimal example:

use std::ops::Deref;

struct UserId(String);

impl Deref for UserId {
    type Target = String;

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

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

fn main() {
    let id = UserId("alice".to_string());

    // Deref coercion: &UserId -> &String -> &str
    greet(&id);

    // Method access on the inner String
    println!("{}", id.len());
}

This works, but it also illustrates the core risk: UserId is no longer clearly distinct from String in many call sites.


Prefer explicit APIs over “smart” wrapper types

A common mistake is to use Deref to make a domain type “feel like” its inner representation. For example, a UserId(String) wrapper might deref to String, or a Meters(f64) wrapper might deref to f64. This often weakens the very invariants the wrapper was meant to protect.

If your type represents a concept with its own semantics, keep the API explicit:

  • expose named methods for domain behavior
  • implement AsRef or Borrow when you need read-only access
  • avoid Deref unless the wrapper truly is a transparent pointer-like abstraction

Good use cases

Deref is appropriate when the wrapper is conceptually a pointer or handle:

  • Box<T>
  • Rc<T>
  • Arc<T>
  • Pin<P>
  • MutexGuard<T> in limited contexts
  • String derefing to str
  • Vec<T> derefing to [T]

These types are designed so that accessing the inner value is the primary operation.

Poor use cases

Avoid Deref when the wrapper encodes meaning:

  • UserId(String)
  • EmailAddress(String)
  • Port(u16)
  • Temperature(f64)
  • DatabaseUrl(String)

In these cases, deref coercion can blur the line between a validated domain object and a raw primitive.


A practical rule: ask whether the wrapper is “transparent”

A good heuristic is this:

Implement Deref only if your type is a transparent abstraction over another type and users should naturally think of it as “the same thing, but managed differently.”

That is why String derefs to str: a String is a growable owned string, but it still behaves like a string in most read-only contexts. By contrast, a UserId is not “just a string”; it is a validated identifier with a specific meaning.

The more your type adds meaning, validation, or constraints, the less suitable Deref becomes.


Use DerefMut even more cautiously

DerefMut is more powerful than Deref because it enables mutation through the wrapper. That makes it easier to accidentally expose internal state in ways that bypass invariants.

For example, if a type validates that a value is always sorted, normalized, or non-empty, DerefMut can allow callers to mutate the inner value directly and violate those guarantees.

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

struct SortedNumbers(Vec<i32>);

impl Deref for SortedNumbers {
    type Target = Vec<i32>;

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

impl DerefMut for SortedNumbers {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

This looks convenient, but it means callers can do this:

fn break_invariant(nums: &mut SortedNumbers) {
    nums.push(-100);
    nums.reverse();
}

If sorted order matters, DerefMut is the wrong abstraction. Prefer methods like insert_sorted, push_checked, or as_slice plus controlled mutation APIs.


Compare common design choices

GoalPreferAvoid
Read-only access to inner dataAsRef, Borrow, named accessorsDeref for domain wrappers
Mutable access with invariant protectionCustom methodsDerefMut
Pointer-like ergonomicsDeref / DerefMutManual forwarding for every method
Domain type safetyNewtype with explicit methodsTransparent deref to primitive
Interoperability with generic codeAsRef<T> or Borrow<T>Implicit deref coercions everywhere

This table is not a hard rulebook, but it captures the trade-offs that matter in real codebases.


Keep method resolution predictable

One subtle downside of Deref is that it changes method lookup. When you call value.method(), Rust may search through multiple deref layers to find a matching method. This is convenient, but it can also make code less obvious to readers.

For example, if a wrapper type and its inner type both define a method with the same name, the one that gets called may not be immediately clear without checking the type definitions. That can be especially confusing in large codebases with nested wrappers.

Best practice

  • Use Deref only when method forwarding is expected and unsurprising.
  • Avoid adding wrapper-specific methods that shadow inner methods.
  • Prefer explicit names for wrapper-specific behavior, such as normalize(), validate(), or inner().

If a reader has to mentally simulate deref chains to understand what a call does, the abstraction is probably too clever.


Expose inner data intentionally

Sometimes you do want to let callers access the wrapped value. The best practice is to make that access explicit.

Common patterns include:

  • as_str()
  • as_slice()
  • inner()
  • into_inner()
  • get()

These methods communicate ownership and intent clearly.

struct EmailAddress(String);

impl EmailAddress {
    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn into_inner(self) -> String {
        self.0
    }
}

This design keeps the wrapper honest: callers can read or consume the value, but they cannot accidentally treat the type as a raw String everywhere.


Use Deref for ergonomics, not for abstraction leakage

A wrapper type should not become a backdoor to the implementation detail it was meant to hide. If you implement Deref just so callers can use every method of the inner type, you are effectively saying the wrapper adds no meaningful abstraction.

That may be acceptable for pointer-like wrappers, but it is usually a smell for domain types.

Ask these questions before implementing Deref

  1. Will users expect this type to behave like the inner type?
  2. Does the wrapper add any invariants or semantic meaning?
  3. Would explicit accessors be clearer?
  4. Could DerefMut allow invalid states?
  5. Will deref coercions improve readability, or just reduce typing?

If the answer to question 2 is yes, be very cautious.


A better pattern for domain wrappers

Suppose you are modeling a validated username. You want to ensure it is non-empty, trimmed, and contains only allowed characters.

A naive Deref<Target = str> implementation might make the type easy to use, but it also makes it easy to forget that validation exists.

A better design:

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Username(String);

impl Username {
    pub fn parse(input: &str) -> Result<Self, &'static str> {
        let trimmed = input.trim();

        if trimmed.is_empty() {
            return Err("username cannot be empty");
        }

        if !trimmed.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
            return Err("username contains invalid characters");
        }

        Ok(Self(trimmed.to_owned()))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

This version is explicit, preserves invariants, and still remains ergonomic enough for everyday use.

fn welcome(user: &Username) {
    println!("Welcome, {}", user.as_str());
}

The extra .as_str() is a small price for preserving the meaning of the type.


When Deref is the right choice

There are still excellent reasons to implement Deref:

  • your type is a smart pointer or ownership wrapper
  • the inner type is the primary interface
  • deref coercion significantly improves ergonomics
  • the abstraction does not rely on hidden invariants

For example, a custom smart pointer used in a memory-managed subsystem may reasonably deref to the managed value. In that case, the wrapper’s purpose is to control access, not to redefine the value’s meaning.

Similarly, if you are building a type that behaves like a string buffer or collection wrapper, Deref can make your API feel natural and reduce boilerplate.

The key is alignment: the wrapper’s semantics should match the deref behavior.


Avoid implementing Deref just to save typing

A common anti-pattern is using Deref as a shortcut to avoid writing forwarding methods. This often creates a type that is easy to use in the short term but difficult to maintain later.

Consider the maintenance cost:

  • adding or changing inner methods can affect public behavior
  • method name collisions become more likely
  • invariants become harder to enforce
  • readers must understand hidden coercions

If your motivation is “so I don’t have to write foo.as_str() everywhere,” that is usually not enough. API clarity is more valuable than shaving off a few characters.


Summary: a practical decision guide

Use the following checklist when deciding whether to implement Deref or DerefMut:

  • Is the wrapper conceptually transparent?
  • Is the inner type the natural primary interface?
  • Would deref coercion improve common call sites?
  • Can mutation through the wrapper preserve invariants?
  • Would explicit methods be clearer for users?

If most answers point toward transparency and ergonomics, Deref may be appropriate. If the type carries meaning, validation, or constraints, prefer explicit accessors and controlled methods.

Rust gives you the tools to build expressive abstractions, but the best abstractions remain visible. Deref should feel like a convenience for an already-obvious relationship, not a mechanism for hiding one.

Learn more with useful resources