Advanced Traits
Course 2 Chapter 2 covered trait basics — definitions, default implementations, trait bounds. This chapter goes deeper into four features that unlock more expressive APIs: associated types, trait objects, operator overloading, and supertraits.
Associated Types
A trait can declare a type placeholder that's part of the trait's own contract, filled in by each implementor — not a separate generic parameter:
Why not a generic trait Iterator<T> instead? A type should have exactly one Item type per implementation — associated types express "one specific type per impl"; a generic trait parameter would instead allow the same type to implement Iterator<u32> and Iterator<String> simultaneously, which isn't the intended shape here at all.
Trait Objects (dyn Trait)
Course 2's generics achieve polymorphism via monomorphization — compile-time, zero-cost, but exactly one concrete type per instantiation. dyn Trait is the runtime alternative: a trait object stores a pointer to the data plus a pointer to a vtable (a table of function pointers for that trait's methods):
This is something monomorphized generics genuinely cannot do: a Vec<T> can only ever hold one concrete T, but Vec<Box<dyn Summary>> holds a heterogeneous collection — different concrete types, unified only by implementing the same trait.
Trait Objects vs. Generics: The Real Trade-off
The standard vocabulary: generics use static dispatch (resolved at compile time, zero runtime cost); dyn Trait uses dynamic dispatch (resolved at runtime via the vtable, a small real cost).
| Generics (Static Dispatch) | dyn Trait (Dynamic Dispatch) | |
|---|---|---|
| Resolved | At compile time (monomorphization) | At runtime (vtable lookup) |
| Runtime cost | Zero | Small — one indirect call per invocation |
| Can mix concrete types? | No — one T per instantiation | Yes — a genuinely heterogeneous collection |
Operator Overloading
The std::ops traits (Add, Sub, Mul, ...) each declare an associated type — Add itself has type Output — directly putting this chapter's first feature to practical use:
Supertraits
A trait can require another trait as a prerequisite, letting a default implementation safely rely on the supertrait's methods:
Any type implementing DisplaySummary must also implement Display — the compiler enforces this at the impl site.
Associated Types
type Item; — one specific type per implementation, part of the trait's own contract.
dyn Trait
Dynamic dispatch via a vtable — enables heterogeneous collections generics can't express.
Operator Overloading
Implementing std::ops traits (Add, Sub, ...) to customize +, -, and similar syntax.
Supertraits
Trait: OtherTrait — requires OtherTrait, letting default methods rely on it safely.
dyn Trait defers the concrete type to runtime entirely. Operator overloading customizes syntax for a known concrete type. Supertraits compose known guarantees together. Different answers to the same underlying question.
dyn Trait requires the trait to be object safe. A trait with a method returning Self (not Box<Self> or similar), or with generic methods, generally cannot be turned into a trait object — the vtable has no way to represent "return the exact concrete Self type" without knowing what Self actually is. This is a real, sometimes-surprising compiler restriction worth knowing by name, even without memorizing every specific rule.
Coding Challenges
Define a trait Container with an associated type Item and a method get(&self, index: usize) -> Option<&Self::Item>. Implement it for a struct StringBag wrapping a Vec
Write a function print_all(items: &[Box
Implement std::ops::Sub for a Point struct so that p1 - p2 works, following this chapter's Add example as a template. Then explain, in your own words, why Add's associated type Output isn't always the same type as Self, using a hypothetical example where it wouldn't be.
📄 View solutionChapter 1 Quick Reference
- type Item; — an associated type; one specific type per trait implementation, not a generic parameter
- dyn Trait — a trait object; dynamic dispatch via a vtable, enables heterogeneous collections
- Static dispatch (generics) — zero runtime cost, one concrete type per instantiation
- Dynamic dispatch (dyn Trait) — small runtime cost, mixes concrete types behind one trait
- impl Add for Type { type Output = ...; fn add(...) } — operator overloading via std::ops
- trait A: B { ... } — a supertrait; implementing A requires also implementing B
- Not every trait is object-safe — methods returning Self or using generics generally block dyn Trait use
- Next chapter: Unsafe Rust — raw pointers, unsafe blocks, when/why to opt out of the borrow checker, basic FFI