Deref Coercions with Functions and Methods
How Rust automatically dereferences smart pointers when passing arguments to functions and calling methods, and the rules that govern when this implicit conversion applies
Deref coercion is the mechanism that lets you pass a &Box<String> to a function that expects &str without writing explicit dereferences. The compiler silently inserts calls to Deref::deref to convert a reference to one type into a reference to the target type. This section focuses on how that coercion plays out specifically when calling functions and methods — where it appears most often and where the rules can feel surprising.
What Deref Coercion Solves
Without deref coercion, every interaction between a smart pointer and a function expecting the inner type would require manual dereferencing. You would write hello(&(*my_box)) instead of hello(&my_box). The friction adds up quickly when smart pointers like Box<T>, Rc<T>, or Arc<T> are passed to APIs designed for references. Deref coercion eliminates that noise by letting the compiler perform the necessary conversion steps automatically, at compile time, with zero runtime cost.
The feature is built on the Deref trait. If a type T implements Deref<Target = U>, then a &T can be coerced to &U wherever a &U is expected. The compiler will also chain multiple deref calls when necessary, as long as each intermediate type implements Deref.
How Function Arguments Trigger Coercion
When you call a function, the compiler checks whether the type of each argument matches the parameter type. If they do not match, and the expected parameter type is a reference, Rust will attempt to coerce the argument by repeatedly calling .deref() until it either reaches a matching reference or runs out of deref implementations.
Consider a function that takes a string slice:
fn greet(name: &str) {
println!("Hello, {name}!");
}
A &String is not the same type as &str, but String implements Deref<Target = str>. The compiler therefore inserts a call to deref on the &String, turning the &String into a &str. All of these calls compile identically:
let name = String::from("Rust");
greet(&name); // &String coerced to &str
greet(name.as_str()); // explicit conversion, same result
The first version works because &String is coerced. The second is explicit, which is also fine but exactly the kind of repetition deref coercion removes.
This same logic extends to nested smart pointers. If you have a Box<String>, a &Box<String> can be coerced all the way to &str — first from &Box<String> to &String (via Box<T>’s Deref), then from &String to &str. The compiler performs the chain silently.
let boxed_name = Box::new(String::from("World"));
greet(&boxed_name); // &Box<String> → &String → &str
The key rule: the coercion only happens when the function parameter is a reference. Deref coercion converts &T to &U; it never turns a T into a U by value. This is why passing a Box<i32> to a function expecting i32 fails — the compiler will not call deref and then dereference the result to produce an owned value.
Compile-time only:
Every deref call is resolved during compilation. The Rust compiler inlines the chain and generates the same machine code as if you had written the dereferences by hand. There is no runtime dispatch or performance penalty.
Chaining Multiple Derefs
The compiler does not stop after a single deref. As long as the current intermediate type implements Deref, the compiler will continue. This allows deeply nested wrappers to collapse into a reference to the innermost type.
To see this in action, build a chain of wrapper structs that each implement Deref to point to the next:
use std::ops::Deref;
struct LayerA<T>(T);
struct LayerB<T>(T);
struct LayerC<T>(T);
impl<T> Deref for LayerA<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
impl<T> Deref for LayerB<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
impl<T> Deref for LayerC<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
Now a function that expects &i32 can accept a &LayerC<LayerB<LayerA<i32>>>:
fn show(n: &i32) {
println!("value is {n}");
}
let chain = LayerC(LayerB(LayerA(42)));
show(&chain); // &LayerC → &LayerB → &LayerA → &i32
The compiler inserts three deref calls. If you compile and run this, the output is value is 42, and the chain resolves entirely at compile time.
Beware of unexpected chains:
If a type in the chain implements Deref in a way that surprises the caller, the compiler will still follow it. The coercion is silent, so the conversion must always be the intended, obvious behavior for the type. An accidental deref chain can mask bugs where a wrapper unintentionally exposes the inner type’s methods.
Method Calls — Why .len() Works on Box<String>
Method resolution uses deref coercion differently from function arguments. When you write value.method(), the compiler searches for a method named method starting at the type T of value, then at &T, then at &mut T, and then it repeatedly dereferences the type (following the Deref chain) to search on the target types.
This means you can call any method that exists on the deref target directly on the smart pointer. For example, String has a .len() method on &self, so a Box<String> gains .len() automatically:
let boxed = Box::new(String::from("hello"));
assert_eq!(boxed.len(), 5); // called via Deref<Target = String>
The method call does not require the outer & that function arguments need. The compiler inserts derefs until it finds a type that has the method. The same applies to Vec<T> methods on Box<Vec<T>>, or str methods on Rc<String>, etc.
However, method resolution only dereferences to find a matching receiver type. It does not dereference the return value or other arguments. And it does not insert an arbitrary number of derefs to satisfy trait bounds on generic type parameters — that is why passing a Box<i32> to a function fn any<T: Fred>(t: T) fails, even if i32: Fred. The function expects an owned T, not a reference, so deref coercion cannot help.
Deref coercion never moves ownership:
If a function takes T by value, you cannot pass a smart pointer wrapping T and expect deref coercion to produce an owned T. The coercion always yields a reference. Attempting to do so will produce a type mismatch error. Use *pointer explicitly if you need to move the inner value.
Mutable Deref Coercion with DerefMut
For mutable references, the DerefMut trait provides the same kind of coercion. If a type implements DerefMut<Target = U>, a &mut T can be coerced to &mut U. This allows a mutable smart pointer to be passed to functions that expect a mutable reference to the inner value.
Imagine a wrapper that holds a Vec<u32> and allows mutable access:
use std::ops::{Deref, DerefMut};
struct Buffer(Vec<u32>);
impl Deref for Buffer {
type Target = Vec<u32>;
fn deref(&self) -> &Vec<u32> { &self.0 }
}
impl DerefMut for Buffer {
fn deref_mut(&mut self) -> &mut Vec<u32> { &mut self.0 }
}
A function that pushes a value into a mutable vector can now receive a &mut Buffer:
fn add_item(v: &mut Vec<u32>, item: u32) {
v.push(item);
}
let mut buf = Buffer(vec![1, 2, 3]);
add_item(&mut buf, 4); // &mut Buffer coerced to &mut Vec<u32>
assert_eq!(*buf, vec![1, 2, 3, 4]);
The compiler inserts the deref_mut call, and the mutable reference flows to the inner vector.
Ergonomic mutable access:
When both Deref and DerefMut are implemented, the type behaves transparently in both immutable and mutable contexts. The caller can treat the wrapper almost exactly like the underlying type when passing references around.
When Deref Coercion Does Not Apply
Understanding where coercion stops is just as important as knowing where it works. Several common scenarios trip up newcomers.
No coercion for owned values. As mentioned, if a function signature is fn process(x: T), passing a smart pointer that Derefs to T will not compile. Coercion converts references to references, not values to values.
No coercion during trait object construction. You cannot pass a Box<Dog> to a function expecting &dyn Animal through a single deref coercion step alone, unless the parameter is &dyn Animal and Box<Dog> derefs to Dog and Dog can be unsized-coerced. Actually, &Box<Dog> can be coerced to &dyn Animal through deref + unsized coercion. But Box<Dog> to Box<dyn Animal> is an unsized coercion, not deref coercion. It's a different mechanism.
No coercion to satisfy generic trait bounds. This is the case from the earlier example. If a function is fn use_fred<T: Fred>(x: T), passing Box<42> fails because Box<i32> does not implement Fred even if i32 does. Deref coercion does not cause Box<i32> to satisfy a trait bound by unwrapping. To make it work, you would need to explicitly dereference: use_fred(*boxed).
No coercion through raw pointers. *const T and *mut T do not implement Deref, so coercions never originate from raw pointers. Unsafe code must manually dereference raw pointers.
Method name collisions. If the wrapper type defines a method with the same name as a method on the deref target, the wrapper's method takes precedence. Method resolution searches the wrapper's own type first. This can obscure the inner type’s method entirely, which is one reason the standard library's Box<T> has no methods.
Common Mistakes and Design Advice
The most frequent mistake with deref coercion is implementing Deref on a type that is not a transparent wrapper. If your type has distinct behavior, or failing to dereference would be a bug, do not implement Deref. Use AsRef or an explicit accessor method instead. Deref coercion permanently commits the type to being used like the target type in every immutable reference context; pulling that commitment back later is a breaking change.
Another pitfall is implementing Deref for a type where the deref target exposes many methods that do not make sense for the wrapper. For example, implementing Deref<Target = u32> on a configuration handle would allow any code to do arithmetic on the handle, which is almost certainly wrong. The compiler will not warn about the mismatch; it will silently let the code compile.
When the coercion chain is long, the mental overhead of tracking which type a variable really is can increase. While the compiler tracks it precisely, human readers of the code may lose the thread. Deeply nested wrappers with deref chains are best refactored into a flatter structure where the ownership and indirection are explicit.
Deref should be infallible and cheap:
The deref method is called implicitly by the compiler, often in places the programmer does not see. It must never fail or perform expensive work. A deref that allocates, locks a mutex, or panics will lead to confusing bugs and compile-time behavior that is impossible to debug by reading the source alone.
Summary
Deref coercion with functions and methods makes smart pointers nearly invisible. You pass a &Box<T> to something expecting &T, and the compiler inserts the deref for you. Method calls traverse the deref chain to find the right receiver, allowing boxed_string.len() to compile directly. Mutable references follow the same path through DerefMut.
The coercion only ever produces references, never owned values. It never satisfies trait bounds on generic types. And it operates entirely at compile time with no runtime cost.
The trade-off is that any type implementing Deref permanently opens the door to silent conversions that can surprise maintainers if the type is not truly a transparent wrapper. Use Deref when your type is its target type in every meaningful way; otherwise, prefer explicit conversion methods.