What are traits in Rust and how do they compare to interfaces?
Learn what traits are in Rust and how they compare to interfaces — default methods, static vs dynamic dispatch, trait objects, and the orphan rule. With code.
Expected Interview Answer
A trait defines a set of methods a type must implement to share behavior, much like an interface in other languages, but with default methods, static generic dispatch, and the ability to implement a trait for types you do not own.
Traits describe shared capabilities — for example Display for printing or Iterator for looping — and any type can implement them. Unlike classic interfaces, traits can provide default method bodies, be used as generic bounds resolved at compile time (static dispatch, zero cost) or behind dyn for runtime dispatch, and coherence rules let you add a trait to an existing type through the orphan rule. This makes traits the backbone of Rust's polymorphism without inheritance.
- Define shared behavior across unrelated types
- Provide default method implementations to reduce boilerplate
- Enable zero-cost static dispatch via generic bounds
- Support runtime polymorphism with trait objects (dyn Trait)
- Let you implement behavior for types you did not define
- Replace inheritance with flexible composition
AI Mentor Explanation
A trait is like the certification 'wicketkeeper' — it lists the skills a player must demonstrate: catching, stumping, standing up to spin. Any player who proves those skills earns the role, regardless of which club they came from. The certificate specifies required abilities without dictating how each keeper trains, just as a trait names required methods without dictating each type's internals.
Step-by-Step Explanation
Step 1
Define the trait
Declare trait Speak { fn say(&self) -> String; } listing required methods, optionally with default bodies.
Step 2
Implement for a type
Write impl Speak for Dog { fn say(&self) -> String { "Woof".into() } } to give the type the behavior.
Step 3
Add default methods
Provide a default body inside the trait so implementers get it free unless they override it.
Step 4
Use as a bound
Constrain generics with fn greet<T: Speak>(x: &T) for compile-time static dispatch with no overhead.
Step 5
Use trait objects
Take &dyn Speak or Box<dyn Speak> when you need runtime polymorphism over different concrete types.
What Interviewer Expects
- Traits define shared behavior a type must implement
- Comparison to interfaces, including default methods
- Static dispatch via generic bounds vs dynamic dispatch via dyn
- Awareness of the orphan/coherence rule
- That Rust uses traits instead of inheritance
- Familiarity with common std traits like Display and Iterator
Common Mistakes
- Saying traits are exactly the same as interfaces
- Forgetting traits can have default method implementations
- Confusing static dispatch (generics) with dynamic dispatch (dyn)
- Thinking you can implement any trait for any type ignoring the orphan rule
- Believing Rust supports classic class inheritance
Best Answer (HR Friendly)
“Traits in Rust describe a set of abilities a type promises to provide, similar to interfaces in other languages. They are more flexible because they can supply default behavior and let you add capabilities to existing types, and Rust uses them instead of class inheritance.”
Code Example
trait Speak {
fn say(&self) -> String;
fn shout(&self) -> String { // default method
format!("{}!", self.say())
}
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn say(&self) -> String { "Woof".to_string() }
}
impl Speak for Cat {
fn say(&self) -> String { "Meow".to_string() }
}
// static dispatch via a generic bound
fn greet<T: Speak>(x: &T) { println!("{}", x.shout()); }
fn main() {
greet(&Dog);
// dynamic dispatch via a trait object
let animals: Vec<Box<dyn Speak>> = vec![Box::new(Dog), Box::new(Cat)];
for a in &animals { println!("{}", a.say()); }
}Follow-up Questions
- What is the difference between static and dynamic dispatch?
- What is the orphan rule and why does it exist?
- When would you use dyn Trait instead of a generic bound?
- How do default methods reduce boilerplate?
- How do trait bounds compose with where clauses?
- Why does Rust favor traits over inheritance?
MCQ Practice
1. How do traits differ from classic interfaces?
Unlike classic interfaces, traits can supply default method bodies that implementers inherit unless they override them.
2. Which gives zero-cost static dispatch?
Generic bounds are monomorphized at compile time, producing static dispatch with no runtime overhead; dyn Trait uses a vtable at runtime.
3. What does the orphan rule restrict?
Coherence requires that at least the trait or the type be defined in your crate, preventing conflicting external implementations.
Flash Cards
What is a trait? — A named set of methods a type must implement to share behavior — Rust's mechanism for polymorphism.
Traits vs interfaces? — Like interfaces, but with default methods, static generic dispatch, and the ability to implement for foreign types.
Static vs dynamic dispatch? — Generic bounds are monomorphized (static, zero-cost); dyn Trait uses a vtable for runtime dispatch.
What is the orphan rule? — You may implement a trait only if the trait or the type is defined in your crate, keeping impls coherent.
Does Rust have inheritance? — No class inheritance; it uses traits and composition to share and constrain behavior instead.
Continue Learning
Related Interview Questions
What is the difference between static and dynamic dispatch in Rust?
medium
What are generics in Rust and how do trait bounds constrain them?
medium
What is the orphan rule in Rust, and how do you implement a foreign trait for a foreign type?
medium
How does monomorphisation affect Rust compile times and binary size, and when do you use dyn instead?
hard