Mojo Traits

A trait defines a set of methods that a struct must implement. Think of a trait as a contract: any struct that adopts the trait agrees to provide specific capabilities. Traits let you write functions that work with any struct that fulfills a contract, without knowing the exact struct type in advance.

The Contract Analogy

  Trait: Printable
  Contract says: "You must provide a method called print_info()"

  struct Dog adopts Printable → must implement print_info()
  struct Car adopts Printable → must implement print_info()

  fn display(item: Printable):   ← works with ANY Printable struct
      item.print_info()

Defining a Trait

trait Shape:
    fn area(self) -> Float64: ...
    fn perimeter(self) -> Float64: ...
    fn name(self) -> String: ...

The ... (ellipsis) means "no body here — implementing structs provide the body." The trait only declares what methods must exist, not how they work.

Implementing a Trait

struct Circle(Shape):
    var radius: Float64

    fn __init__(inout self, r: Float64):
        self.radius = r

    fn area(self) -> Float64:
        return 3.14159 * self.radius * self.radius

    fn perimeter(self) -> Float64:
        return 2.0 * 3.14159 * self.radius

    fn name(self) -> String:
        return "Circle"

struct Square(Shape):
    var side: Float64

    fn __init__(inout self, s: Float64):
        self.side = s

    fn area(self) -> Float64:
        return self.side * self.side

    fn perimeter(self) -> Float64:
        return 4.0 * self.side

    fn name(self) -> String:
        return "Square"

The struct name in parentheses (Shape) declares that the struct implements that trait. Mojo checks at compile time that every required method exists with the correct signature.

Using Trait-Typed Parameters

Write a function that accepts any struct conforming to a trait. The function works with Circle, Square, Triangle, or any future Shape you add.

fn print_shape_info[T: Shape](shape: T):
    print(shape.name())
    print("  Area:", shape.area())
    print("  Perimeter:", shape.perimeter())

fn main():
    var c = Circle(5.0)
    var s = Square(4.0)

    print_shape_info(c)
    print_shape_info(s)

Output:

Circle
  Area: 78.53975
  Perimeter: 31.4159
Square
  Area: 16.0
  Perimeter: 16.0
Trait usage flow:

  print_shape_info[T: Shape](shape)
         │
         ├── T = Circle → calls Circle.area(), Circle.perimeter()
         └── T = Square → calls Square.area(), Square.perimeter()

Built-In Traits

Mojo's standard library provides traits that unlock built-in operations for your structs.

Stringable

Allows your struct to work with str() and string formatting.

trait Stringable:
    fn __str__(self) -> String: ...

Sized

Allows your struct to work with len().

trait Sized:
    fn __len__(self) -> Int: ...

Comparable

Allows your struct to be compared with == and !=.

trait EqualityComparable:
    fn __eq__(self, other: Self) -> Bool: ...
    fn __ne__(self, other: Self) -> Bool: ...

Implementing Multiple Traits

struct Temperature(Stringable, EqualityComparable):
    var celsius: Float64

    fn __init__(inout self, c: Float64):
        self.celsius = c

    fn __str__(self) -> String:
        return String(self.celsius) + "°C"

    fn __eq__(self, other: Self) -> Bool:
        return self.celsius == other.celsius

    fn __ne__(self, other: Self) -> Bool:
        return self.celsius != other.celsius

fn main():
    var t1 = Temperature(37.0)
    var t2 = Temperature(37.0)
    var t3 = Temperature(100.0)

    print(str(t1))          # 37.0°C
    print(t1 == t2)         # True
    print(t1 == t3)         # False

Trait Inheritance

Traits can build on other traits. A struct that implements a child trait must satisfy both the child and parent trait requirements.

trait Animal:
    fn sound(self) -> String: ...

trait Pet(Animal):
    fn owner(self) -> String: ...
    # Also requires 'sound' from Animal

struct Dog(Pet):
    fn sound(self) -> String:
        return "Woof"

    fn owner(self) -> String:
        return "Alice"

fn main():
    var d = Dog()
    print(d.sound())   # Woof
    print(d.owner())   # Alice
Trait hierarchy:

  Animal (requires: sound)
     │
     └── Pet (requires: sound + owner)
              │
              └── Dog (implements both) ✓

Traits vs Python Duck Typing

Python duck typing:            Mojo traits:
  "If it walks like a duck      "Contract verified at compile
   and quacks like a duck,       time — if you say you are a
   it is a duck."                Shape, Mojo checks before
  (checked at runtime —          running any code."
   error possible mid-run)       (error caught early)

Key Takeaways

A trait defines a method contract that structs agree to fulfill. Declare a struct as implementing a trait by listing the trait name in parentheses after the struct name. Mojo verifies implementation completeness at compile time. Functions parameterized on a trait work with any conforming struct. Built-in traits like Stringable, Sized, and EqualityComparable integrate your structs with Mojo's standard operations. Traits can inherit from other traits to build richer contracts.

Leave a Comment

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