Deref Isn’t Inheritance: What Rust Interviews Are Really Testing For

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

MechanismWhat it actually does
DerefOperator 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.