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."

Leave a Comment

Your email address will not be published. Required fields are marked *