16.10Code reuse without inheritance
If you're coming from C++, Java, or Python, a question has been building for the whole chapter: where's inheritance? The answer is that Rust doesn't have it, on purpose, and this lesson is the honest accounting of what you use instead. It's a lesson C++ veterans specifically search for, because the instinct to reach for a base class is strong and Rust redirects it three different ways depending on what you were actually trying to do.
Inheritance did two jobs
The reason "what replaces inheritance?" doesn't have a one-word answer is that inheritance bundled two separate things together, and Rust unbundles them. Classical inheritance gave you (1) code reuse: a subclass gets the parent's fields and methods without rewriting them, and (2) polymorphism: a function taking a Shape works on any subclass of Shape, calling the right overridden method. These are different needs that happened to share a feature, and Rust provides each one separately, which turns out to be cleaner.
Reuse through composition
For the reuse half, Rust's answer is composition: instead of a type being another type (inheritance), it contains one (a field). Where you'd reach for class Manager extends Employee, Rust uses a struct with an Employee field:
struct Employee {
name: String,
salary: u64,
}
impl Employee {
fn pay_stub(&self) -> String {
format!("{}: ${}", self.name, self.salary)
}
}
struct Manager {
employee: Employee, // composition: a Manager *has an* Employee
reports: u32,
}
impl Manager {
fn summary(&self) -> String {
format!("{} manages {} people", self.employee.pay_stub(), self.reports)
}
}
Manager reuses Employee's data and behavior by holding one and delegating to it (self.employee.pay_stub()). This is the "has-a" relationship, and the wider software-design world has long argued it's usually the better default anyway, summed up in the maxim "prefer composition over inheritance." Rust simply makes composition the only option, sidestepping the well-known problems of deep inheritance hierarchies (fragile base classes, surprising overrides, the diamond problem). The small cost is that delegation is explicit: you write self.employee.pay_stub() rather than inheriting pay_stub silently. That explicitness is generally considered a feature, not a tax.
Shared behavior through default methods
For the "many types share an implementation" half of reuse, the tool is default trait methods (lesson 16.2). Where a base class provides a method all subclasses inherit, a trait provides a default method all implementers inherit:
trait Greet {
fn name(&self) -> String;
fn hello(&self) -> String { // shared default, "inherited" by all
format!("Hello, I'm {}", self.name())
}
}
Every type implementing Greet gets hello for free and only supplies name. That's the inheritance-of-behavior pattern, achieved with a trait instead of a base class, and crucially a type can implement many traits (gaining many sets of shared behavior), where most languages let a class inherit from only one parent. Composition for shared data, default trait methods for shared behavior.
Polymorphism through trait objects
For the polymorphism half, "a function that works on any subtype, calling the right method", the tool is trait objects (lesson 16.9). A function taking &dyn Draw works on any type implementing Draw, dispatching to the right draw at runtime, which is exactly what a function taking a Shape* and calling a virtual method did in C++. So the polymorphism you'd get from a base-class pointer, you get from a trait object, without the class hierarchy.
Key insight
Inheritance bundled reuse and polymorphism; Rust unbundles them. Reuse of data is composition (a field). Reuse of behavior is default trait methods. Polymorphism is trait objects (dyn Trait). Each was a job inheritance did, and each gets a focused tool. The result has no base classes, no override surprises, and no single-inheritance limit, and a type can mix in as many traits as it needs. What you lose is the convenience of writing one extends and getting everything at once; what you gain is that each kind of sharing is explicit and independent.
When you'll miss it, and what to reach for
Honesty, since C++ veterans will test this. There are moments the lack of inheritance stings: a deep hierarchy of types that genuinely share a lot of fields and behavior and need runtime polymorphism, all at once, is more verbose in Rust because you assemble it from three tools instead of one keyword. When you feel that, the move is to ask which job you actually need: shared fields (compose, with a field), shared behavior (a trait with defaults), runtime dispatch over a mix (a dyn Trait), and often you discover you needed only one or two of the three, not the whole bundle. The cases where you truly need all three deeply nested are rarer than inheritance-shaped habits suggest, and when they arise, a trait with default methods plus composition plus Box<dyn Trait> covers them, just spelled out rather than implied.
Best practice
When you catch yourself wanting inheritance, name the specific thing you want. "I want to reuse these fields", compose. "I want these types to share this method", default trait method. "I want to treat different types uniformly at runtime", trait object. Reaching for "a base class" is usually reaching for two or three of these at once; separating them almost always yields a cleaner design, and frequently reveals you only needed one.
Quiz time
Question #1
What two distinct jobs did classical inheritance bundle together, and what does Rust use for each?
Show solution
Code reuse (a subclass gets the parent's fields/methods) and polymorphism (one function handles any subtype, calling the right method). Rust splits them: composition (a field) for reusing data, default trait methods for reusing behavior, and trait objects (dyn Trait) for runtime polymorphism. Each job gets a focused tool instead of one bundled feature.
Question #2
Rust has no class Manager extends Employee. How do you give Manager access to Employee's data and behavior?
Show solution
Composition: give Manager a field of type Employee (employee: Employee) and delegate to it (self.employee.pay_stub()). Manager has an Employee rather than being one. The delegation is explicit, which is the "prefer composition over inheritance" approach that's widely considered the better default anyway.
Question #3
A coworker wants a base class so several types can share one validate method and be stored together in a list and processed uniformly. Which Rust tools address each part?
Show solution
Sharing the validate method: a trait with a default validate method that all the types implement (lesson 16.2). Storing them together and processing uniformly: a Vec<Box<dyn TheTrait>> of trait objects with dynamic dispatch (lesson 16.9). No base class needed; the trait covers both the shared behavior and the polymorphism.
That completes the traits chapter, the structural center of Rust and the clearest proof of the "shorter than C++" thesis. The summary is next. Then chapter 17 finally writes out the thing you've used implicitly since chapter 9 and deferred from lesson 10.8: lifetimes, the annotations that let references live in structs and function signatures.