Language Internals
Part 5 of 11 · Rust Language ProficiencyTraits, Generics, impl Trait & dyn Trait
Traits, generics, impl Trait vs dyn Trait.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
What is a trait?
Answer
A set of methods other types can implement. It is the reuse tool, not a base class.
L2
What does a generic function compile to?
Answer
A copy of the function for each concrete type that uses it. That is monomorphization.
L3
What is impl Trait in argument position?
Answer
Sugar for a generic bounded by that trait. The caller still picks one concrete type.
L4
What is dyn Trait?
Answer
A trait object. The pointer carries a vtable. The trait must be object safe.
L5
When is a method not object safe?
Answer
When it uses generics, Self by value in a way the vtable cannot erase, or otherwise cannot be called through a fat pointer.
L6
What is the orphan rule?
Answer
You cannot implement a foreign trait for a foreign type. Wrap the type in a newtype you own.
L7
Why Into or AsRef on a public function?
Answer
AsRef of str borrows without allocating; Into of String is convenient but allocates when handed a shared str.
Failure modes
dyn on the hot path
A known type still goes through a vtable and a heap box.
Generic explosion
Every combination monomorphizes and the binary grows.
Orphan impl
An impl of a foreign trait for a foreign type does not compile.
Trait object of a generic method
The trait is not object safe, so Box of dyn Trait is rejected.
Misconceptions
A trait is a TypeScript interface at runtime.
TypeScript interfaces erase. A Rust generic copies code. dyn Trait keeps a vtable.
impl Trait returns a trait object.
impl Trait in return position is one hidden concrete type, still static.
You can implement Display for a type you do not own.
The orphan rule forbids it. Newtype the value.
Interviewer traps
Reaching for Box dyn Trait for every parameter.
If the caller has one type, use a generic. Save dyn for mixed collections.
Comparing a Python Protocol to a Rust trait object.
A Protocol is structural and erased by the checker. dyn Trait exists at runtime.
Design scenario
Same prompt for every reader.
Requirements
A Summarize trait, impl Trait or a generic for the request, and Box of dyn Summarize for the sidebar.
Traffic / scale
The request path is hot. The sidebar is small and mixed.
Latency
The hot path should monomorphize. The sidebar can pay for a vtable.
Consistency
A new summary type implements the trait you own, or a newtype if the trait is foreign.
Availability
An object-unsafe method must not be added if the sidebar uses dyn.
Failure assumptions
- Every parameter is boxed as dyn Trait.
- Display is implemented for a third-party struct directly.
Constraints
- Stay on traits. Do not redesign error types yet.
Prompt
A notifier accepts one article type on the request path, and a sidebar holds mixed summaries.
Static or dynamic
Prefer
Generic when the type is known
The compiler copies the function for that type. No vtable on the hot path.
- impl Trait in arguments is a generic.
- Return-position impl Trait hides one type.
- Code size can grow.
Alternative
dyn Trait when types mix
One function, a fat pointer, and object-safe methods only.
- A Vec of boxes can mix implementers.
- Generic methods break object safety.
- The call is indirect.
Overview
Traits are how Rust shares behavior. They are nominal and coherence-checked. A generic or impl Trait monomorphizes. dyn Trait is the vtable. TypeScript interfaces and Python Protocols are structural and erased, so a list of them is naturally mixed.
Comparative
| Style | TypeScript | Python | Rust |
|---|---|---|---|
| Interface | interface | Protocol | trait |
| Static generic | erased | erased | monomorphized |
| Runtime polymorphism | objects | objects | dyn Trait |
Decisions
- 1
Step 1 Define trait Summarize
- nextStep 2 impl Summarize for Article
- 2
Step 2 impl Summarize for Article
- nextStep 3 Concrete type known at compile time
- ?
Step 3 Concrete type known at compile time
- yesStep 4a Generic T: Summarize or impl Trait - monomorphized, static dispatch
- noStep 4b Box dyn Summarize - vtable, dynamic dispatch
- 4
Step 4a Generic T: Summarize or impl Trait - monomorphized, static dispatch
- nextStep 5a Inlining possible, one copy of the code per type
- 5
Step 4b Box dyn Summarize - vtable, dynamic dispatch
- nextStep 5b Heterogeneous Vec of trait objects
- trait has generic methods or returns SelfFailure path - not dyn compatible, use generics
- 6
Step 5a Inlining possible, one copy of the code per type
- 7
Step 5b Heterogeneous Vec of trait objects
- 8
Failure path - not dyn compatible, use generics
Lesson map
Traits, Generics, impl Trait & dyn Trait
Traits, generics, impl Trait vs dyn Trait.
Architecture. Step 1 Define trait Summarize Ready. Step 2 impl Summarize for Article Ready. Step 3 Concrete type known at compile time Ready. Step 4a Generic T: Summarize or impl Trait - monomorphized, static dispatch Ready. Step 4b Box dyn Summarize - vtable, dynamic dispatch Ready. Step 5a Inlining possible, one copy of the code per type Ready. Step 5b Heterogeneous Vec of trait objects Ready. Failure path - not dyn compatible, use generics Ready
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB A["Step 1 Define trait Summarize Ready"] B["Step 2 impl Summarize for Article Ready"] C["Step 3 Concrete type known at compile time Ready"] D["Step 4a Generic T: Summarize or impl Trait - monomorphized, static dispatch Ready"] E["Step 4b Box dyn Summarize - vtable, dynamic dispatch Ready"] H["Step 5a Inlining possible, one copy of the code per type Ready"] G["Step 5b Heterogeneous Vec of trait objects Ready"] F["Failure path - not dyn compatible, use generics Ready"] A -->|continues| B B -->|continues| C C -->|yes| D C -->|no| E D -->|continues| H E -->|continues| G E -->|trait has generic methods or returns Self| F
Press Run. Snippets must be self-contained — no network, files, or native modules.
Rosetta — trait, interface, Protocol
trait Summarize {
fn summary(&self) -> String;
}
struct Article {
title: String,
}
impl Summarize for Article {
fn summary(&self) -> String {
self.title.clone()
}
}
fn notify(item: &impl Summarize) {
println!("{}", item.summary());
}interface Summarize {
summary(): string;
}
class Article implements Summarize {
constructor(public title: string) {}
summary() {
return this.title;
}
}
function notify(item: Summarize) {
console.log(item.summary());
}from typing import Protocol
class Summarize(Protocol):
def summary(self) -> str: ...
class Article:
def __init__(self, title: str) -> None:
self.title = title
def summary(self) -> str:
return self.title
def notify(item: Summarize) -> None:
print(item.summary())Rust checks coherence. The TypeScript parameter is already a runtime object. The Python Protocol disappears after the checker.
Rosetta — impl Trait versus dyn Trait
fn static_sum() -> impl Summarize {
Article { title: "static".into() }
}
fn dyn_sum() -> Box<dyn Summarize> {
Box::new(Article { title: "dyn".into() })
}function make(): Summarize {
return new Article("dyn");
}
const many: Summarize[] = [new Article("a"), new Article("b")];def make() -> Summarize:
return Article("dyn")
many: list[Summarize] = [Article("a"), Article("b")]Return-position impl Trait is still one concrete type. A heterogeneous Vec<Box<dyn Summarize>> needs an object-safe trait. TypeScript and Python lists of interfaces are mixed by default.
Rosetta — From and Into
fn takes_string(s: impl Into<String>) {
let owned: String = s.into();
println!("{owned}");
}function takesString(s: string) {
console.log(s);
}def takes_string(s: str) -> None:
print(s)Into lets a caller pass a literal or a String. TypeScript and Python usually have one string type at runtime, so the widening is smaller.
Interview Q&A
What is the orphan rule?
Answer
You cannot implement a foreign trait for a foreign type. Wrap the foreign type in a newtype you own, then implement the trait for the wrapper.
What is object safety?
Answer
The rules that let a method be called through a vtable. Generic methods and some uses of Self by value are not object safe.
Is impl Trait a trait object?
Answer
No. In argument position it is a generic. In return position it names one hidden concrete type. dyn Trait is the trait object.
When do you monomorphize?
Answer
Static dispatch often inlines but can grow the binary; dyn adds an indirect call but keeps one copy.
When do you box dyn Trait?
Answer
When one collection must hold different implementers. Keep the trait object safe.
How is a Python Protocol different?
Answer
It is structural and erased. A Rust trait impl is nominal, and dyn Trait still exists at runtime.
How is a TypeScript interface different?
Answer
Callers already pass an object. There is no monomorphized copy of the function for each class.
Why Into on a library boundary?
Answer
AsRef of str borrows without allocating; Into of String is convenient but allocates when handed a shared str. rustc 1.98.1 on edition 2024 accepts both.
Pitfalls
- Boxing every argument as dyn Trait.
- Adding a generic method and breaking existing trait objects.
- Implementing a foreign trait for a foreign type.
- Using impl Trait in return position when callers must name the type.
- Cloning inside a trait method that could have returned a borrow.
Take a notify function and a sidebar list. Mark which one should be generic and which one should store Box of dyn Trait, and why.