Mojo Inheritance
Inheritance lets one struct build on another, reusing and extending its capabilities without rewriting existing code. Mojo achieves inheritance through traits and composition patterns rather than the classical class-based inheritance of languages like Java or Python. Understanding this approach helps you design flexible, reusable code structures.
The Specialization Concept
Vehicle (general)
├── properties: speed, fuel
└── behaviors: move(), refuel()
│
├── Car (specialized Vehicle)
│ ├── inherits: speed, fuel, move(), refuel()
│ └── adds: doors, air_conditioning()
│
└── Motorcycle (specialized Vehicle)
├── inherits: speed, fuel, move(), refuel()
└── adds: sidecar, wheelie()
Trait-Based Inheritance Pattern
Mojo structs do not directly inherit from other structs. Instead, you use traits to define shared behavior, then implement those traits in specialized structs. This is a cleaner model that avoids the fragile base class problem found in classical inheritance.
trait Vehicle:
fn speed(self) -> Int: ...
fn fuel_type(self) -> String: ...
fn describe(self) -> String: ...
struct Car(Vehicle):
var top_speed: Int
var doors: Int
fn __init__(inout self, top_speed: Int, doors: Int):
self.top_speed = top_speed
self.doors = doors
fn speed(self) -> Int:
return self.top_speed
fn fuel_type(self) -> String:
return "Gasoline"
fn describe(self) -> String:
return "Car with " + String(self.doors) + " doors"
struct ElectricBike(Vehicle):
var top_speed: Int
var battery_kwh: Float64
fn __init__(inout self, top_speed: Int, battery: Float64):
self.top_speed = top_speed
self.battery_kwh = battery
fn speed(self) -> Int:
return self.top_speed
fn fuel_type(self) -> String:
return "Electric"
fn describe(self) -> String:
return "Electric bike, " + String(self.battery_kwh) + " kWh battery"
fn show_vehicle[T: Vehicle](v: T):
print(v.describe())
print(" Max speed:", v.speed(), "km/h")
print(" Fuel:", v.fuel_type())
fn main():
var car = Car(180, 4)
var ebike = ElectricBike(45, 15.0)
show_vehicle(car)
show_vehicle(ebike)
Output:
Car with 4 doors Max speed: 180 km/h Fuel: Gasoline Electric bike, 15.0 kWh battery Max speed: 45 km/h Fuel: Electric
Composition: "Has-A" Relationships
Composition means one struct contains another struct as a field. Where inheritance says "a Car IS-A Vehicle," composition says "a Car HAS-A Engine." Composition often produces more flexible designs than deep inheritance hierarchies.
struct Engine:
var horsepower: Int
var cylinders: Int
fn __init__(inout self, hp: Int, cyl: Int):
self.horsepower = hp
self.cylinders = cyl
fn describe(self) -> String:
return String(self.cylinders) + "-cylinder, " + String(self.horsepower) + " hp"
struct SportsCar:
var engine: Engine # HAS-A Engine
var brand: String
fn __init__(inout self, brand: String, hp: Int, cyl: Int):
self.brand = brand
self.engine = Engine(hp, cyl)
fn info(self):
print(self.brand, "→ Engine:", self.engine.describe())
fn main():
var ferrari = SportsCar("Ferrari", 710, 12)
ferrari.info() # Ferrari → Engine: 12-cylinder, 710 hp
Composition Diagram:
SportsCar
├── brand: "Ferrari"
└── engine: Engine
├── horsepower: 710
└── cylinders: 12
Access: ferrari.engine.describe()
Comparing Inheritance Approaches
Approach | Description | Mojo Support ------------------|------------------------------|------------- Trait inheritance | Parent trait requires methods| Yes — traits chain Composition | Struct contains struct | Yes — field of struct type Classical inherit | Struct extends struct fields | Limited in Mojo
Extending Behavior with Traits
You extend a base trait by creating a child trait that inherits its requirements and adds new ones. Any struct implementing the child must satisfy both sets of requirements.
trait Printable:
fn to_string(self) -> String: ...
trait Serializable(Printable):
fn to_json(self) -> String: ...
# Must also implement to_string() from Printable
struct Config(Serializable):
var name: String
var value: Int
fn __init__(inout self, name: String, value: Int):
self.name = name
self.value = value
fn to_string(self) -> String:
return self.name + " = " + String(self.value)
fn to_json(self) -> String:
return '{"' + self.name + '": ' + String(self.value) + '}'
fn main():
var cfg = Config("timeout", 30)
print(cfg.to_string()) # timeout = 30
print(cfg.to_json()) # {"timeout": 30}
Default Method Implementations in Traits
Mojo traits can provide default implementations for methods. Implementing structs inherit the default and can override it if needed.
trait Greetable:
fn name(self) -> String: ...
fn greet(self) -> String:
return "Hello, I am " + self.name()
struct Robot(Greetable):
fn name(self) -> String:
return "R2-D2"
# greet() is inherited from the trait default
struct Human(Greetable):
fn name(self) -> String:
return "Alice"
fn greet(self) -> String:
return "Hi there! I am " + self.name() # custom override
fn main():
var r = Robot()
var h = Human()
print(r.greet()) # Hello, I am R2-D2 (uses trait default)
print(h.greet()) # Hi there! I am Alice (uses own override)
Key Takeaways
Mojo achieves code reuse through trait inheritance and composition rather than classical struct-to-struct inheritance. Traits define contracts that multiple structs fulfill, enabling polymorphic functions. Composition builds complex types by embedding simpler structs as fields. Child traits inherit parent requirements and add new ones. Default trait method implementations reduce repetition while still allowing overrides. Prefer composition when relationships are "has-a" and traits when relationships are "behaves-like."
