Traits
Course 1 built individual types with their own methods. This chapter introduces the tool for describing shared behavior across different types — a trait, directly comparable to Go's own interfaces (go2-2).
Defining and Implementing a Trait
A trait declares a method signature; a type opts in by writing its own impl Trait for Type block — Article and Tweet implement summarize completely differently.
Default Implementations
A trait method can include a default body — implementing types may use it as-is, or override it:
Traits as Parameters (impl Trait)
notify accepts any type implementing Summary — the exact same "program to an interface, not an implementation" idea Go's interfaces exist for.
Trait Bounds
impl Trait is sugar for a more general, more powerful syntax — trait bounds — needed when a constraint can't be expressed with the sugar alone. Two parameters that must be the same concrete type is exactly that case:
impl Trait can't express "these two parameters must be the same type" — only a shared generic parameter with a trait bound can.
Contrast With Go's Interfaces
Go interfaces are satisfied implicitly — structural typing means if a type happens to have the right methods, it satisfies the interface automatically, with no explicit declaration anywhere. Rust traits are explicit: a type must write impl Trait for Type even if it already happens to have a method with a matching signature.
| Go | Rust | |
|---|---|---|
| Interface/trait satisfaction | Implicit — structural typing | Explicit — impl Trait for Type required |
| Accidental satisfaction | Possible — matching methods are enough | Impossible — must be declared on purpose |
Rust's explicitness prevents a type from accidentally satisfying a trait it was never intended to, at the cost of one extra impl block.
trait Definition
Declares method signatures a type can opt into implementing.
Default Implementation
A body a trait method already has; implementers may use it or override it.
impl Trait Parameter
Accept any type implementing a trait — sugar for the more general trait-bound syntax.
Trait Bound (<T: Trait>)
Needed when multiple parameters must share the exact same concrete type.
impl block, demanding they implement it too — the same "compiler tells you everywhere that needs updating" theme from Course 1's enum-variant gotcha, just showing up here as the case a default deliberately avoids.
impl SomeExternalTrait for SomeExternalType when neither belongs to you. This exists specifically to prevent two different crates from providing conflicting implementations of the same trait for the same type, which would make it genuinely ambiguous which one applies. It's a real, sometimes-surprising restriction for newcomers coming from more permissive languages — but it's protecting a real guarantee (coherence), not an arbitrary limitation.
Coding Challenges
Define a trait Describable with a method describe(&self) -> String that has a default implementation returning "No description available.". Implement it for a struct Book (overriding the default) and a struct Widget (using the default, via an empty impl block).
📄 View solutionWrite a function print_description(item: &impl Describable) using Challenge 1's trait, then explain why this same function could NOT be used to compare two Describable items of potentially different concrete types for equality, and what syntax change would be needed to require them to be the same type.
📄 View solutionExplain, in your own words, why Go's implicit interface satisfaction could lead to a type accidentally satisfying an interface it wasn't designed for, and why Rust's explicit impl Trait for Type requirement makes that specific mistake impossible.
📄 View solutionChapter 2 Quick Reference
- trait Name { fn method(&self) -> T; } — declares shared behavior; types opt in via impl Trait for Type
- Default implementations — a trait method can have a body; implementers may use it or override it
- fn f(x: &impl Trait) — accepts any type implementing Trait; sugar for a trait bound
- fn f<T: Trait>(a: &T, b: &T) — a trait bound; needed when parameters must share the same concrete type
- T: TraitA + TraitB — multiple bounds; where clauses improve readability for longer bound lists
- Go interfaces are satisfied implicitly (structural); Rust traits require an explicit impl block — no accidental satisfaction
- Adding a defaulted trait method doesn't break existing implementers; adding one with no default does
- The orphan rule: either the trait or the type must be local to your own crate
- Next chapter: Generics — generic functions/structs, trait bounds on generics, and a contrast with Go's own generics