The setup
Deref coercion is one of the most common “gotcha” questions in Rust interviews, because it’s genuinely easy to mistake for inheritance if you’re coming from Java or C++. This post is my working notes from testing it hands-on.
The illusion
rust
struct Animal;
struct Dog;
impl Animal {
fn eat(&self) { println!("Animal eats"); }
}
impl Deref for Dog {
type Target = Animal;
fn deref(&self) -> &Animal { &Animal }
}
fn main() {
let d = Dog;
d.eat(); // works — but Dog has no eat() of its own
}
Dog doesn’t have eat(). The compiler fails to find it, then automatically calls Dog::deref(), gets an &Animal, and tries again. This chained lookup is why it feels like inherited methods.
Where it breaks
Trait bounds don’t get this courtesy. Given:
rust
trait Speak { fn speak(&self); }
impl Speak for Animal { fn speak(&self) { println!("Animal speaks"); } }
fn make_it_speak<T: Speak>(x: &T) { x.speak(); }
make_it_speak(&Dog); // error[E0277]: the trait bound `Dog: Speak` is not satisfied
This is the exact moment the illusion collapses. Deref operates only at method-name resolution time — a compiler convenience for call sites. Generic bounds check the nominal type directly. Dog was never granted Speak; it only ever had indirect, borrowed access to Animal‘s copy of it.
The fix — and the real lesson
rust
impl Speak for Dog {
fn speak(&self) { println!("Dog speaks (not borrowed from Animal!)"); }
}
Once Dog has its own real implementation, two things happen: the trait bound is satisfied, and — critically — d.speak() now calls Dog‘s own method, silently shadowing the one reachable via Deref. Direct implementations always win over coercion.
Takeaway
| Mechanism | What it actually does |
|---|---|
Deref | Operator overload + method-lookup fallback at call sites |
Trait bound (T: Trait) | Compile-time proof the nominal type implements the trait |
| Inheritance (OOP) | Subtyping + shared/overridable behavior + often runtime dispatch |
Deref gives you convenience, not a type hierarchy. No subtyping, no virtual dispatch, no overriding — just an optional shortcut the compiler is willing to chase.