Rust 55 🦀 Dereference Trait (Deref) and Implicit Deref Coercion
The previous chapter introduced Box<T> as a smart pointer that owns a heap value. When Box<String> is used, method calls on the box reach the inner String, and function arguments that expect &str accept a &Box<String>. This transparency is not a special property of Box. It is the Deref trait at work. The trait lets a type opt into behaving like a reference, and the compiler inserts dereferences automatically at coercion sites. Understanding Deref explains why Box, Rc, Arc, String, and Vec all feel transparent, and why writing a newtype wrapper often requires implementing Deref to keep it usable.
Key point: Deref is not about overloading the * operator for convenience. It is about describing a type as a transparent container for another type, so the compiler can insert dereferences where a reference is needed. Implementing Deref extends the set of places where the new type can be used, at the cost of a small amount of clarity about which type is actually receiving the call.
Why Deref exists
The wrapper transparency problem. A newtype like struct Meters(f64) is a distinct type, but semantically it is a number. Method calls on Meters should reach the inner f64 when no method on Meters matches. Without Deref, the developer must write self.0.abs() everywhere. With Deref, self.abs() works, and the compiler derefs through the wrapper.
The smart-pointer transparency problem. A Box<T> is a pointer, but code should be able to call T‘s methods on it without unwrapping. Rc<T> and Arc<T> have the same need. The Deref trait is the mechanism that makes smart pointers behave like the values they own.
The coercion problem. Function signatures often take references to the base type, such as &str or &[T]. A caller with a String or a Vec<T> should be able to pass a reference without an explicit conversion. Deref coercion inserts the conversion automatically.
The chain problem. A Box<Rc<String>> should deref through multiple layers to reach String. The compiler applies the deref chain repeatedly until it finds a type that matches the expected signature. This makes nested smart pointers usable without explicit unwrapping.
The method resolution problem. When a method is called, the compiler searches the receiver’s type, then the type it derefs to, and so on. This is why methods of the inner type are callable on a wrapper without any syntactic change.
The newtype trade-off. Implementing Deref on a newtype makes the newtype transparent for method calls, but it also weakens the type safety that motivated the newtype in the first place. A newtype that derefs to its inner type can be passed where the inner type is expected, which defeats the purpose of the wrapper. The design decision is whether the transparency is worth the loss of distinction.
a. The Deref and DerefMut traits
The Deref trait has one method, deref, which returns a reference to the target type. DerefMut adds a mutable version.
The Deref trait
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
The associated type Target is the type the wrapper derefs to. The ?Sized bound allows the target to be an unsized type such as str or [T]. The deref method returns a shared reference to the target.
The DerefMut trait
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
DerefMut inherits from Deref and adds deref_mut, which returns a mutable reference. A type that implements DerefMut can be used in contexts that require mutable access to the target.
The Box implementation
The standard library’s Box<T> implements both traits.
impl<T: ?Sized> Deref for Box<T> {
type Target = T;
fn deref(&self) -> &T {
&**self
}
}
The implementation returns &**self, which is a reference to the inner value. The double dereference goes through the Box to reach the value.
The custom implementation
A newtype can implement Deref to expose its inner value.
struct Meters(f64);
impl std::ops::Deref for Meters {
type Target = f64;
fn deref(&self) -> &f64 {
&self.0
}
}
fn main() {
let m = Meters(10.0);
println!("{}", m.abs()); // Calls f64::abs via deref.
}
The method abs is not defined on Meters. The compiler derefs m to f64 and finds the method there.
The DerefMut for mutable access
If the inner value needs to be mutated, the wrapper must also implement DerefMut.
use std::ops::{Deref, DerefMut};
struct Wrapper(String);
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
impl DerefMut for Wrapper {
fn deref_mut(&mut self) -> &mut String { &mut self.0 }
}
fn main() {
let mut w = Wrapper(String::from("hi"));
w.push_str(" there");
println!("{}", *w);
}
The push_str method mutates the inner String through DerefMut.
b. Deref coercion
Deref coercion is the compiler’s automatic insertion of deref calls when a reference of one type is used where a reference of another type is expected.
The coercion sites
Deref coercion applies at three sites: function arguments, method receivers, and let bindings with explicit type annotations. The compiler inserts as many deref calls as needed to make the types match.
fn takes_str(s: &str) { }
fn main() {
let s = String::from("hello");
takes_str(&s); // &String coerces to &str.
}
The argument is &String, the parameter is &str. The compiler inserts a deref call to convert.
The coercion chain
When the types do not match directly, the compiler follows the deref chain. Box<Rc<String>> derefs to Rc<String>, which derefs to String, which derefs to str. The compiler tries each step until the target type matches.
use std::rc::Rc;
fn takes_str(s: &str) { }
fn main() {
let b = Box::new(Rc::new(String::from("hello")));
takes_str(&b); // &Box<Rc<String>> → &str
}
The chain is &Box<Rc<String>> → &Rc<String> → &String → &str. The compiler applies the deref chain once per step.
The mutability requirement
Deref coercion to a mutable reference requires DerefMut at every step. A Box<T> provides both Deref and DerefMut. A type that only implements Deref cannot be coerced to a mutable reference.
fn takes_str_mut(s: &mut String) { }
fn main() {
let mut b = Box::new(String::from("hello"));
takes_str_mut(&mut b); // Requires DerefMut.
}
If Box did not implement DerefMut, the call would fail to compile.
The method receiver coercion
Method calls on a receiver are subject to a more permissive coercion. The compiler tries self, &self, and &mut self for each type in the deref chain. It stops at the first method that matches.
struct Wrapper(String);
impl std::ops::Deref for Wrapper {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
fn main() {
let w = Wrapper(String::from("hello"));
println!("{}", w.len()); // String::len via deref.
}
The compiler looks for len on Wrapper, finds nothing, derefs to String, and finds len there.
The method resolution order
The search order is: T, &T, &mut T, then U, &U, &mut U, where U is the deref target. The compiler stops at the first match. This is why a method defined on the wrapper shadows a method with the same name on the inner type.
struct Wrapper(String);
impl Wrapper {
fn len(&self) -> usize { 999 }
}
impl std::ops::Deref for Wrapper {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
fn main() {
let w = Wrapper(String::from("hello"));
println!("{}", w.len()); // 999, the wrapper's method.
}
The wrapper’s method wins because it is found first in the search order.
The explicit deref
When the inner method is wanted and the wrapper has a method of the same name, the developer must use explicit deref syntax.
println!("{}", (*w).len()); // 5, String::len
The *w derefs the wrapper to reach the inner String, and the method call applies to that.
c. When to implement Deref
Implementing Deref is a design decision with trade-offs. The trait is powerful, but it is also easy to misuse.
The smart-pointer use case
Smart pointers implement Deref because they own or reference another value. Box<T>, Rc<T>, Arc<T>, and Cow<T> all implement Deref to expose the inner value. This is the canonical use case.
The newtype transparency use case
A newtype that semantically acts like its inner type can implement Deref to inherit the inner type’s methods.
struct Email(String);
impl std::ops::Deref for Email {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
fn send(to: &str) { }
fn main() {
let e = Email(String::from("a@b.com"));
println!("{}", e.len()); // String::len
send(&e); // &Email → &str
}
The Email wrapper adds semantic meaning but remains usable wherever a string is expected.
The misuse case
Implementing Deref on a newtype that is not semantically a transparent wrapper weakens the type safety that motivated the newtype. A Meters newtype that derefs to f64 can be added to a Feet newtype that also derefs to f64, which defeats the purpose of having separate types.
struct Meters(f64);
struct Feet(f64);
// Both implement Deref to f64.
// The compiler cannot prevent adding Meters to Feet.
The rule is: implement Deref only when the wrapper is a transparent container, not when the wrapper exists to distinguish one semantic from another.
The Deref inheritance trap
A type that implements Deref inherits all of the target’s methods. If the target has a method that should not be exposed, Deref exposes it anyway. This is the reason the Rust API guidelines recommend against Deref for inheritance.
struct Buffer(Vec<u8>);
impl std::ops::Deref for Buffer {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> { &self.0 }
}
fn main() {
let mut b = Buffer(Vec::new());
b.push(1); // Vec::push is now exposed.
}
If Buffer is meant to control how elements are added, Deref breaks that control.
The Deref versus DerefMut distinction
Implementing Deref alone gives shared access. Implementing DerefMut additionally gives mutable access. A wrapper that should not allow mutation of its inner value implements only Deref.
struct ReadOnly(Vec<u8>);
impl std::ops::Deref for ReadOnly {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> { &self.0 }
}
// No DerefMut. Mutation through the wrapper is impossible.
This is the pattern for read-only views of owned data.
The alternative: explicit methods
Instead of Deref, a wrapper can expose the inner value through explicit methods. This preserves the wrapper’s control over its interface.
struct Email(String);
impl Email {
fn as_str(&self) -> &str { &self.0 }
}
fn send(to: &str) { }
fn main() {
let e = Email(String::from("a@b.com"));
send(e.as_str());
}
The caller writes as_str() explicitly. The wrapper controls exactly what is exposed.
The standard library precedent
The standard library implements Deref for smart pointers and for String (target str) and Vec<T> (target [T]). These are all cases where the type is a container for another type. The pattern is the reference: Deref is for containers, not for domain distinctions.
Complete Example Session
// ============================================
// PART 1: THE BASIC DEREF
// ============================================
fn main() {
let b = Box::new(5);
assert_eq!(*b, 5);
assert_eq!(*(&b), 5);
}
// ============================================
// PART 2: CUSTOM DEREF FOR A NEWTYPE
// ============================================
use std::ops::Deref;
struct Meters(f64);
impl Deref for Meters {
type Target = f64;
fn deref(&self) -> &f64 { &self.0 }
}
fn main() {
let m = Meters(10.0);
assert_eq!(m.abs(), 10.0);
assert_eq!(*m, 10.0);
}
// ============================================
// PART 3: DEREFMUT FOR MUTABLE ACCESS
// ============================================
use std::ops::DerefMut;
struct Wrapper(String);
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
impl DerefMut for Wrapper {
fn deref_mut(&mut self) -> &mut String { &mut self.0 }
}
fn main() {
let mut w = Wrapper(String::from("hello"));
w.push_str(" world");
assert_eq!(*w, "hello world");
}
// ============================================
// PART 4: DEREF COERCION AT FUNCTION ARGUMENTS
// ============================================
fn takes_str(s: &str) { }
fn main() {
let s = String::from("hello");
takes_str(&s); // &String → &str
let b = Box::new(String::from("hello"));
takes_str(&b); // &Box<String> → &str
}
// ============================================
// PART 5: THE COERCION CHAIN
// ============================================
use std::rc::Rc;
fn takes_str(s: &str) { }
fn main() {
let nested = Box::new(Rc::new(String::from("hello")));
takes_str(&nested); // &Box<Rc<String>> → &str
}
// ============================================
// PART 6: METHOD RECEIVER COERCION
// ============================================
struct Email(String);
impl Deref for Email {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
fn main() {
let e = Email(String::from("a@b.com"));
assert_eq!(e.len(), 5); // String::len
assert!(e.contains('@')); // String::contains
}
// ============================================
// PART 7: METHOD SHADOWING
// ============================================
struct WithLen(String);
impl WithLen {
fn len(&self) -> usize { 999 }
}
impl Deref for WithLen {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
fn main() {
let w = WithLen(String::from("hello"));
assert_eq!(w.len(), 999); // Wrapper's method wins.
assert_eq!((*w).len(), 5); // Explicit deref reaches String.
}
// ============================================
// PART 8: DEREF VERSUS EXPLICIT METHODS
// ============================================
struct Email2(String);
impl Email2 {
fn as_str(&self) -> &str { &self.0 }
}
fn send(to: &str) { }
fn main() {
let e = Email2(String::from("a@b.com"));
send(e.as_str()); // Explicit call.
}
// ============================================
// PART 9: READ-ONLY WRAPPER
// ============================================
struct ReadOnly(Vec<u8>);
impl Deref for ReadOnly {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> { &self.0 }
}
fn main() {
let r = ReadOnly(vec![1, 2, 3]);
assert_eq!(r.len(), 3);
// r.push(4); // Error: no DerefMut.
}
// ============================================
// PART 10: THE STANDARD LIBRARY PATTERNS
// ============================================
fn main() {
// String derefs to str.
let s = String::from("hello");
let slice: &str = &s;
// Vec<T> derefs to [T].
let v = vec![1, 2, 3];
let slice: &[i32] = &v;
// Box<T> derefs to T.
let b = Box::new(5);
let r: &i32 = &b;
// Rc<T> and Arc<T> deref to T.
let rc = std::rc::Rc::new(5);
let r: &i32 = &rc;
}
The ten parts progressed from basic Deref through custom implementations, DerefMut, coercion at function arguments, the coercion chain, method receiver coercion, method shadowing, the alternative of explicit methods, read-only wrappers, and the standard library patterns. The same wrapper examples ran through every part, showing how Deref extends a type’s usability.
Quick Reference
The Deref Traits
| Trait | Method | Returns |
|---|---|---|
Deref | deref(&self) | &Target |
DerefMut | deref_mut(&mut self) | &mut Target |
Deref Coercion Sites
| Site | Example |
|---|---|
| Function argument | takes_str(&string) |
| Method receiver | string.len() |
let with type annotation | let s: &str = &string; |
Coercion Chains
| From | To | Steps |
|---|---|---|
&String | &str | 1 |
&Box<String> | &str | 2 |
&Box<Rc<String>> | &str | 3 |
&Vec<T> | &[T] | 1 |
&Box<[T; N]> | &[T] | 2 |
Standard Library Deref Implementations
| Type | Target |
|---|---|
Box<T> | T |
Rc<T> | T |
Arc<T> | T |
String | str |
Vec<T> | [T] |
PathBuf | Path |
Cow<'a, T> | T |
When to Implement Deref
| Scenario | Implement |
|---|---|
| Smart pointer | Yes |
| Transparent newtype | Sometimes |
| Domain distinction | No |
| Control over interface | No |
Best Practices
✅ Do This:
// Implement Deref for smart pointers.
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
} // ✅
// Implement DerefMut only when mutation is intended.
impl<T> DerefMut for MyBox<T> {
fn deref_mut(&mut self) -> &mut T { &mut self.0 }
} // ✅
// Use Deref for transparent containers.
struct Email(String);
impl Deref for Email { /* target String */ } // ✅
// Use explicit methods when the wrapper controls the interface.
impl Email {
fn as_str(&self) -> &str { &self.0 }
} // ✅
// Understand method shadowing.
(*wrapper).method() // ✅ Explicit deref when needed
❌ Don’t Do This:
// Don't implement Deref for domain distinctions.
struct Meters(f64);
struct Feet(f64);
impl Deref for Meters { /* ... */ } // ❌ Loses distinction
// Don't use Deref to inherit methods you don't want to expose.
impl Deref for Buffer { /* exposes Vec::push */ } // ❌
// Don't implement DerefMut when mutation should be prevented.
impl DerefMut for ReadOnly { /* ... */ } // ❌ Breaks read-only intent
// Don't rely on Deref for type conversion semantics.
// Use From and Into for explicit conversions. // ❌
// Don't forget the search order.
wrapper.method() // ❌ Might call wrapper's method, not inner
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Method not found | Deref not implemented | Implement Deref or call explicitly |
| Wrapper’s method shadows inner | Search order | Use explicit * deref |
Cannot coerce to &mut | Missing DerefMut | Implement DerefMut |
| Unexpected method exposed | Deref inherits all | Use explicit methods instead |
| Type safety lost | Deref on domain newtype | Don’t implement Deref |
| Coercion fails | Chain doesn’t reach target | Check the deref chain |
| Ambiguous method call | Multiple deref targets | Use explicit type annotation |
| Deref recursion | Deref returns self | Return the inner value |
Real-World Examples
1. Custom Box
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
2. Transparent Email Newtype
struct Email(String);
impl Deref for Email {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
3. Read-Only Wrapper
impl Deref for ReadOnly {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> { &self.0 }
}
// No DerefMut.
4. Coercion to &str
fn takes_str(s: &str) { }
takes_str(&String::from("hi")); // &String → &str
5. Coercion to &[T]
fn takes_slice(s: &[i32]) { }
takes_slice(&vec![1, 2, 3]); // &Vec<i32> → &[i32]
6. Chained Coercion
fn takes_str(s: &str) { }
let nested = Box::new(Rc::new(String::from("hi")));
takes_str(&nested); // &Box<Rc<String>> → &str
7. DerefMut for Mutation
w.push_str(" world"); // String::push_str via DerefMut
8. Explicit Deref
(*wrapper).len() // Reach inner type's method
9. Deref on Rc
let rc = Rc::new(5);
assert_eq!(*rc, 5);
10. Deref on Arc
let arc = Arc::new(String::from("hi"));
assert_eq!(arc.len(), 2);
Visual
The Deref Chain
┌────────────────────────────────────────────────────────────┐
│ DEREF COERCION CHAIN │
│ │
│ &Box<Rc<String>> │
│ │ │
│ │ deref │
│ v │
│ &Rc<String> │
│ │ │
│ │ deref │
│ v │
│ &String │
│ │ │
│ │ deref │
│ v │
│ &str │
│ │
│ The compiler follows the chain until the types match. │
│ Each step is a separate Deref implementation. │
│ │
└────────────────────────────────────────────────────────────┘
Method Resolution Order
┌────────────────────────────────────────────────────────────┐
│ METHOD RESOLUTION SEARCH ORDER │
│ │
│ For receiver type T with deref target U: │
│ │
│ 1. Try T::method(self) │
│ 2. Try T::method(&self) │
│ 3. Try T::method(&mut self) │
│ 4. Try U::method(self) │
│ 5. Try U::method(&self) │
│ 6. Try U::method(&mut self) │
│ 7. Continue up the deref chain. │
│ │
│ The first match wins. │
│ A method on T shadows a method of the same name on U. │
│ Use (*t).method() to skip the wrapper's methods. │
│ │
└────────────────────────────────────────────────────────────┘
Deref vs DerefMut
┌────────────────────────────────────────────────────────────┐
│ Deref vs DerefMut │
│ │
│ Deref │
│ ┌──────────────────────────────────────────────────┐ │
│ │ fn deref(&self) -> &Target │ │
│ │ Shared access only. │ │
│ │ Coerces &Wrapper to &Target. │ │
│ │ Method calls that need &self work. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ DerefMut │
│ ┌──────────────────────────────────────────────────┐ │
│ │ fn deref_mut(&mut self) -> &mut Target │ │
│ │ Mutable access. │ │
│ │ Coerces &mut Wrapper to &mut Target. │ │
│ │ Method calls that need &mut self work. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ DerefMut requires Deref. │
│ Implement only Deref to prevent mutation through deref. │
│ │
└────────────────────────────────────────────────────────────┘
The Newtype Trade-Off
┌────────────────────────────────────────────────────────────┐
│ NEWTYPE DEREF TRADE-OFF │
│ │
│ WITH DEREF │
│ ┌──────────────────────────────────────────────────┐ │
│ │ struct Email(String); │ │
│ │ impl Deref for Email { type Target = String; } │ │
│ │ │ │
│ │ email.len() → String::len │ │
│ │ send(email) → coerces to &str │ │
│ │ email.push('!') → String::push (if DerefMut) │ │
│ │ │ │
│ │ Convenient. Inherits all String methods. │ │
│ │ Cannot prevent invalid mutation. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ WITHOUT DEREF │
│ ┌──────────────────────────────────────────────────┐ │
│ │ struct Email(String); │ │
│ │ impl Email { │ │
│ │ fn as_str(&self) -> &str { &self.0 } │ │
│ │ } │ │
│ │ │ │
│ │ email.as_str() → explicit method │ │
│ │ send(email.as_str()) → explicit conversion │ │
│ │ │ │
│ │ Less convenient. │ │
│ │ Controls exactly what is exposed. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ Choose based on whether the wrapper is a container │
│ or a domain distinction. │
│ │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
Deref | Trait with one method, deref, returning &Target |
DerefMut | Trait inheriting Deref, returning &mut Target |
Target | Associated type; the deref destination |
| Deref coercion | Automatic deref insertion at coercion sites |
| Coercion sites | Function arguments, method receivers, let with annotation |
| Coercion chain | Compiler follows derefs until types match |
| Search order | T, &T, &mut T, then U, &U, &mut U |
| Method shadowing | Wrapper’s method wins over inner’s |
| Explicit deref | (*wrapper).method() reaches the inner type |
| Standard implementations | Box, Rc, Arc, String, Vec |
Key takeaways:
Derefdescribes a type as a transparent container. It answers the question: what does this type look like when it is dereferenced?Box<T>derefs toT,Stringderefs tostr, and a custom newtype derefs to whatever it wraps.- Deref coercion is the compiler’s automatic insertion of derefs. At function arguments, method receivers, and annotated
letbindings, the compiler follows the deref chain until the types match. - The coercion chain can be multiple steps.
Box<Rc<String>>coerces to&strby followingBox→Rc→String→str. Each step is a separateDerefimplementation. - Method resolution follows the deref chain. The compiler searches the receiver’s type and each deref target in turn. The first matching method wins, which means a method on the wrapper shadows the same method on the inner type.
DerefMutenables mutable access. A type that implementsDerefMutcan be coerced to a mutable reference. A type that implements onlyDerefcannot.- Implementing
Derefweakens type safety. A newtype that derefs to its inner type can be used wherever the inner type is expected, which defeats the purpose of having a distinct type. Derefis for containers, not for domain distinctions. Smart pointers and transparent wrappers are the appropriate use cases.MetersandFeetare not, because the wrapper exists precisely to distinguish them.- Explicit methods are the alternative. A wrapper that should control its interface exposes the inner value through named methods like
as_str()instead of implementingDeref. This preserves the wrapper’s control at the cost of convenience.
Remember: Deref is the trait that makes smart pointers and transparent wrappers feel like the values they contain. It defines what a type looks like when dereferenced, and the compiler uses that definition to insert dereferences automatically at function arguments, method receivers, and annotated bindings. The coercion chain follows the deref implementations until the types match, and method resolution follows the same chain until a method is found. DerefMut extends the trait with mutable access, and its absence prevents mutation through deref. Implementing Deref is a design decision: it is correct for containers and smart pointers, and questionable for domain newtypes that exist precisely to be distinct from their inner type. The standard library’s implementations on Box, Rc, Arc, String, and Vec are the reference examples, and they show the trait doing exactly what it was designed to do.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!