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.
