Deref and DerefMut
How the Deref and DerefMut traits let custom types use the dereference operator and enable automatic coercions in Rust
The Deref and DerefMut traits are the mechanism that lets you write *my_smart_pointer on a custom type and have it behave like a reference to the contained value. They also power one of Rust's most ergonomic features: automatic deref coercions, where the compiler silently inserts .deref() calls to make method calls and function arguments work without ceremony.
The Deref and DerefMut Traits
Both traits live in std::ops. Deref provides immutable access to an inner value; DerefMut extends that to mutable access. Here is their definition:
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
DerefMut requires Deref to be implemented first. The associated type Target names the type you get back when dereferencing — for Box<T> it is T, for String it is str. The ?Sized bound on Target allows the target to be unsized, which is why String can deref to str.
Why These Traits Exist
Without Deref, any wrapper type — whether a heap-allocated Box, a reference-counted Rc, or your own newtype — would force you to manually extract the inner value every time you wanted to call a method on it. A Vec<u8> could not be passed to a function that expects a &[u8] without a manual conversion. A Box<dyn Fn()> could not be called with () because the function call syntax works on the type inside the box.
Deref solves this by telling the compiler “when I appear in a place that expects a reference to my Target, use the reference to my inner data instead”. This single trait eliminates reams of forwarding methods and makes smart pointers feel like ordinary references.
How Deref Works
When the compiler encounters *value, it first checks whether the type of value implements Deref. If it does, the expression becomes *Deref::deref(&value). In other words, you get an immutable reference to the target and then apply the dereference operator to that reference. For mutable access, the compiler uses DerefMut::deref_mut.
Method calls are more interesting. Suppose you write my_value.some_method(). Rust’s method resolution does not only look at the type of my_value; it also automatically dereferences through as many layers as needed to find a type that has some_method. This includes both built-in reference dereferencing and calls to .deref(). That is why you can call str methods directly on a String — String implements Deref<Target = str>, so the compiler finds str’s methods.
The same logic applies when passing arguments to functions. If a function expects &str and you pass &my_string, the compiler coerces &String to &str by calling Deref::deref. These automatic conversions are called deref coercions, and they can chain through multiple levels of Deref implementations.
Implementing Deref
Consider a tuple struct that wraps a u8 but adds some behaviour. We want to be able to use *my_number and call u8 methods directly.
use std::ops::Deref;
struct HoldsANumber(u8);
impl HoldsANumber {
fn prints_the_number_times_two(&self) {
println!("{}", self.0 * 2);
}
}
impl Deref for HoldsANumber {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let my_number = HoldsANumber(20);
// Uses Deref to add 20 to the inner u8
println!("{}", *my_number + 20); // prints 40
// u8 methods available directly
println!("{:?}", my_number.checked_sub(100)); // prints None
my_number.prints_the_number_times_two(); // prints 40
}
The deref method returns a reference to the u8 inside the struct. When we write *my_number, Rust calls deref to get &u8 and then applies the built-in dereference operator to obtain the u8. When we call checked_sub, Rust’s method resolution finds the method on u8 through the Deref chain.
Target Type Must Match the Contained Data:
The Target type you choose must be the type that your struct logically owns or refers to. If you set Target to a type that is not directly accessible from your struct, you will not be able to return a valid reference from deref.
Implementing DerefMut
Once Deref is in place, adding DerefMut is a matter of providing a mutable version. The trait itself does not require you to restate the Target — it inherits it from Deref.
use std::ops::{Deref, DerefMut};
impl DerefMut for HoldsANumber {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
fn main() {
let mut my_number = HoldsANumber(20);
*my_number = 30; // assigns through mutable dereference
println!("{}", *my_number); // 30
}
With DerefMut, the assignment *my_number = 30 first calls deref_mut to obtain a &mut u8, then writes 30 into that location. The same logic applies when you pass a &mut smart pointer to a function that expects a mutable reference to the target.
DerefMut Requires Mutable Binding:
You can only call deref_mut when the value itself is mutable. If you try to use *my_number = 30 when my_number is not declared mut, the compiler will reject it. DerefMut does not bypass Rust’s mutability rules.
Deref Coercions in Detail
Deref coercions are the automatic insertion of deref calls to reconcile type mismatches. They happen at coercion sites: function arguments, method receivers, and the right-hand side of let when the left-hand side expects a reference type.
fn print_slice(slice: &str) {
println!("{}", slice);
}
fn main() {
let owned = String::from("hello");
print_slice(&owned); // &String coerces to &str
let boxed: Box<String> = Box::new(String::from("world"));
// Two coercions: Box<String> -> String -> str
let s: &str = &boxed;
println!("{}", s); // prints "world"
}
In the call print_slice(&owned), the function expects &str but we pass &String. Because String: Deref<Target = str>, the compiler calls Deref::deref on the &String to produce a &str. The assignment let s: &str = &boxed shows multiple layers: the compiler dereferences Box<String> to String, then String to str.
These coercions also let you call a method that does not exist on the outer type.
use std::rc::Rc;
fn main() {
let rc_string: Rc<String> = Rc::new(String::from("find me"));
// Rc<String> derefs to String, which derefs to str; str::find is called.
assert_eq!(rc_string.find('m'), Some(5));
}
The method call rc_string.find('m') works because Rust resolves find by looking through the Deref chain: Rc<String> → String → str.
Coercions Make APIs Ergonomically Transparent:
When your type implements Deref sensibly, users of your library can treat a smart pointer almost identically to the value it holds. They do not need to call .as_ref(), .deref(), or write explicit * in many common cases.
There is one important limitation: deref coercions only apply to resolve concrete type conflicts. They do not kick in to satisfy trait bounds on generic type parameters. If a function requires T: AsRef<Path> and you have a Box<PathBuf>, the coercion does not change T to PathBuf; Box<PathBuf> does not implement AsRef<Path> just because it can be derefed to one. Coercion works on the concrete argument, not on the type variable.
The Golden Rule — Deref Is for Smart Pointers
The Rust standard library explicitly recommends that Deref and DerefMut be implemented only for smart pointers. A smart pointer is a type that acts as a pointer to some data and also has additional bookkeeping (like reference counting, heap allocation, or a current-position cursor). The canonical examples are Box<T>, Rc<T>, Arc<T>, Vec<T> (which derefs to [T]), and String (which derefs to str).
The rationale is that Deref fundamentally changes how a type behaves: it automatically shadows the interface of the inner type. If you implement it on a type that is not semantically a pointer, callers will be confused about what the type actually is. Consider a Character struct with many fields where Deref is implemented to deref to the character’s hit points — an i8.
use std::ops::Deref;
struct Character {
name: String,
strength: u8,
dexterity: u8,
hit_points: i8,
// ... other fields
}
impl Deref for Character {
type Target = i8;
fn deref(&self) -> &Self::Target {
&self.hit_points
}
}
fn main() {
let hero = Character {
name: "Aragorn".into(),
strength: 10,
dexterity: 12,
hit_points: 100,
};
let mut hit_points_vec: Vec<i8> = vec![];
hit_points_vec.push(*hero); // What exactly are we pushing?
println!("{:?}", hit_points_vec);
}
Pushing *hero onto a vector reads as if you are adding a character, but actually you are pushing its hit points. The meaning of * is overloaded in a way that obscures the real structure. The Character type is not a pointer; it is a broad collection of attributes. A dedicated method like hero.hit_points would be far clearer.
Resist the Urge to Deref Just for Convenience:
Implementing Deref on a non-pointer type to make its inner methods available is an anti-pattern. It can make code unreadable, leads to surprising implicit conversions, and breaks the semantic contract that * and . are transparent pointer operations. Always ask: does this type fundamentally represent a pointer to its Target? If the answer is no, write a named accessor instead.
Common Mistakes and Misconceptions
Several pitfalls appear repeatedly when developers first encounter Deref and DerefMut.
Implementing Deref to “inherit” all methods of the inner type is tempting but wrong outside of smart pointers. This creates what the Rust community calls “deref abuse”. The resulting code compiles but is hard to follow because it hides ownership and type boundaries.
Assuming deref coercions apply to type parameters leads to hard-to-diagnose errors. For example, you might expect fn foo<T: AsRef<str>>(t: T) to accept a Box<String> because Box<String> can be coerced to &str. It will not. Coercion happens at the call site after the generic parameter is resolved, not before.
Confusing auto-deref in method calls with ownership can cause performance surprises. Every call through a Deref chain involves pointer indirection, which is cheap but not free. In performance-sensitive code, holding a &Target explicitly may be beneficial.
Forgetting that DerefMut does not relax borrowing rules is another trap. You cannot obtain a mutable reference through DerefMut while an immutable reference exists elsewhere, even if you think the smart pointer’s metadata is immutable while the target is mutable. The borrow checker treats the smart pointer itself as borrowed mutably when you call deref_mut.
Practical Example: A Selector Smart Pointer
A common pattern in low-level libraries is a cursor that points into a collection. A Selector<T> that holds a vector and a current index is a legitimate smart pointer: it logically references one element at a time.
use std::ops::{Deref, DerefMut};
struct Selector<T> {
elements: Vec<T>,
current: usize,
}
impl<T> Selector<T> {
fn new(elements: Vec<T>) -> Self {
Selector { elements, current: 0 }
}
fn advance(&mut self) {
if self.current + 1 < self.elements.len() {
self.current += 1;
}
}
}
impl<T> Deref for Selector<T> {
type Target = T;
fn deref(&self) -> &T {
&self.elements[self.current]
}
}
impl<T> DerefMut for Selector<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.elements[self.current]
}
}
fn main() {
let mut sel = Selector::new(vec!['a', 'b', 'c']);
// Deref gives read access to the current element
println!("current: {}", *sel); // prints 'a'
println!("is alphabetic: {}", sel.is_alphabetic()); // true
// DerefMut lets us mutate the selected element
*sel = 'x';
sel.advance();
println!("next: {}", *sel); // prints 'b'
}
The Selector behaves exactly like a mutable reference to the current element. You can call any method of T directly on the selector, and *sel gives the current value. The struct also carries its own bookkeeping (current index), which is exactly the justification for a smart pointer. This design is clear, ergonomic, and follows the semantic rule.
Deref, DerefMut and Other Traits
DerefMut requires Deref as a supertrait — you cannot implement mutable dereference without immutable dereference. This is a natural requirement: if something can be mutated through a pointer, it should also be readable through that pointer.
There is no automatic relationship between Deref and the conversion traits From/Into or the reference-borrowing traits AsRef/Borrow. A type that implements Deref does not automatically get a From impl, nor does it automatically satisfy an AsRef bound. Each trait serves a distinct purpose: Deref is for pointer semantics and coercion, AsRef is for explicit cheap reference conversions in generic code, and From is for owned value conversions.
Deref and Coercion Versus AsRef:
Prefer AsRef bounds in generic functions when you want to accept many types that can produce a reference to a specific target. Use Deref only when the type truly is a pointer. Coercion acts automatically on concrete types, but AsRef is the tool you reach for in generic contexts.
Summary
Deref and DerefMut underpin Rust’s smart pointer ecosystem. They give Box, Rc, Vec, String and user-defined pointer types the ability to behave like ordinary references, while also enabling automatic coercions that eliminate repetitive .as_ref() calls.
The key insight is that these traits should only be used when your type is semantically a pointer to its Target. Implementing Deref on a general aggregate type might compile, but it violates expectations and produces confusing code. When applied correctly, Deref makes custom smart pointers as natural to use as built-in references — no boilerplate forwarding, no manual unwrapping.
If your type is not a smart pointer but you still want to expose a reference to some inner data, AsRef and AsMut are the traits to reach for.