Mojo Operator Overloading

Operator overloading lets you define what operators like +, -, *, and == mean for your custom struct types. Instead of calling a method like v1.add(v2), you write v1 + v2, which reads naturally and makes your types behave like built-in types.

Why Overload Operators

Without overloading:           With overloading:
  var c = a.add(b)               var c = a + b
  var d = c.multiply(3.0)        var d = c * 3.0
  if a.equals(b):                if a == b:

  Readable? Barely.              Reads like math.

The Vector2D Example

struct Vector2D:
    var x: Float64
    var y: Float64

    fn __init__(inout self, x: Float64, y: Float64):
        self.x = x
        self.y = y

    fn __add__(self, other: Self) -> Self:
        return Vector2D(self.x + other.x, self.y + other.y)

    fn __sub__(self, other: Self) -> Self:
        return Vector2D(self.x - other.x, self.y - other.y)

    fn __mul__(self, scalar: Float64) -> Self:
        return Vector2D(self.x * scalar, self.y * scalar)

    fn __eq__(self, other: Self) -> Bool:
        return self.x == other.x and self.y == other.y

    fn __str__(self) -> String:
        return "(" + String(self.x) + ", " + String(self.y) + ")"

fn main():
    var v1 = Vector2D(3.0, 4.0)
    var v2 = Vector2D(1.0, 2.0)

    var sum  = v1 + v2
    var diff = v1 - v2
    var scaled = v1 * 2.0

    print(str(sum))     # (4.0, 6.0)
    print(str(diff))    # (2.0, 2.0)
    print(str(scaled))  # (6.0, 8.0)
    print(v1 == v2)     # False

Operator Dunder Method Map

Operator  | Dunder Method   | Example
----------|-----------------|----------------------------
  +       | __add__         | a + b
  -       | __sub__         | a - b
  *       | __mul__         | a * b
  /       | __truediv__     | a / b
  //      | __floordiv__    | a // b
  %       | __mod__         | a % b
  **      | __pow__         | a ** b
  ==      | __eq__          | a == b
  !=      | __ne__          | a != b
  <       | __lt__          | a < b
  <=      | __le__          | a <= b
  >       | __gt__          | a > b
  >=      | __ge__          | a >= b
  -x      | __neg__         | -a (unary minus)
  len()   | __len__         | len(a)
  str()   | __str__         | str(a)
  []      | __getitem__     | a[i]
  []=     | __setitem__     | a[i] = v

Comparison Operators for Sorting

struct Score:
    var player: String
    var points: Int

    fn __init__(inout self, player: String, points: Int):
        self.player = player
        self.points = points

    fn __lt__(self, other: Self) -> Bool:
        return self.points < other.points

    fn __gt__(self, other: Self) -> Bool:
        return self.points > other.points

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

fn main():
    var alice = Score("Alice", 95)
    var bob   = Score("Bob",   80)

    if alice > bob:
        print(alice.player, "scored higher")   # Alice scored higher
    elif alice < bob:
        print(bob.player, "scored higher")
    else:
        print("Tied")

Indexing with __getitem__ and __setitem__

struct Matrix2x2:
    var data: SIMD[DType.float64, 4]

    fn __init__(inout self, a: Float64, b: Float64, c: Float64, d: Float64):
        self.data = SIMD[DType.float64, 4](a, b, c, d)

    fn __getitem__(self, index: Int) -> Float64:
        return self.data[index]

    fn __setitem__(inout self, index: Int, value: Float64):
        self.data[index] = value

fn main():
    var m = Matrix2x2(1.0, 2.0, 3.0, 4.0)
    print(m[0])    # 1.0
    print(m[3])    # 4.0
    m[1] = 99.0
    print(m[1])    # 99.0

Unary Operators

struct Temperature:
    var celsius: Float64

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

    fn __neg__(self) -> Self:
        return Temperature(-self.celsius)

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

fn main():
    var t = Temperature(25.0)
    var inv = -t
    print(str(t))     # 25.0C
    print(str(inv))   # -25.0C

In-Place Operators

In-place operators like += modify the left operand directly. Define __iadd__ and similar methods to support them.

struct Counter:
    var value: Int

    fn __init__(inout self, v: Int):
        self.value = v

    fn __iadd__(inout self, amount: Int):
        self.value += amount

    fn __isub__(inout self, amount: Int):
        self.value -= amount

fn main():
    var c = Counter(10)
    c += 5
    print(c.value)   # 15
    c -= 3
    print(c.value)   # 12

Key Takeaways

Operator overloading maps Mojo operators to dunder methods you define in your struct. The compiler calls __add__ when it sees a + b, __eq__ for a == b, and so on. Overloading makes custom types feel natural to use. Define only the operators your type logically supports — adding two file paths makes no sense, so do not define __add__ for a path struct. Comparison operators unlock sorting and ordering. Index operators __getitem__ and __setitem__ give your struct array-style access.

Leave a Comment

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