Mojo Generics

Generics let you write one function or struct that works correctly for many different types without duplicating code. You define the logic once using a type placeholder, and the compiler generates a separate optimized version for each concrete type you actually use. The result is type-safe, fast, and DRY (Don't Repeat Yourself).

The Vending Machine Analogy

Without generics — one machine per item:
  CandyMachine.dispense()  → only dispenses candy
  ChipsMachine.dispense()  → only dispenses chips
  DrinkMachine.dispense()  → only dispenses drinks

With generics — one universal machine:
  VendingMachine[T].dispense() → dispenses ANY item T
  VendingMachine[Candy].dispense()  → dispenses candy
  VendingMachine[Chips].dispense()  → dispenses chips
  VendingMachine[Drink].dispense()  → dispenses drinks

One blueprint, infinite specializations.

Generic Functions

A generic function uses a type parameter in square brackets. The compiler substitutes the actual type when you call the function.

fn swap[T: AnyType](inout a: T, inout b: T):
    var temp = a
    a = b
    b = temp

fn main():
    var x = 10
    var y = 20
    swap(x, y)
    print(x, y)   # 20 10

    var p = "hello"
    var q = "world"
    swap(p, q)
    print(p, q)   # world hello
Compiler generates two separate functions:
  swap[Int]:    var temp: Int = a; a = b; b = temp
  swap[String]: var temp: String = a; a = b; b = temp

Same source, different machine code — each optimal for its type.

Generic Structs

struct Pair[T: AnyType, U: AnyType]:
    var first:  T
    var second: U

    fn __init__(inout self, first: T, second: U):
        self.first  = first
        self.second = second

fn main():
    var name_age  = Pair[String, Int]("Alice", 30)
    var lat_lng   = Pair[Float64, Float64](35.6895, 139.6917)
    var flag_count = Pair[Bool, Int](True, 42)

    print(name_age.first, name_age.second)       # Alice 30
    print(lat_lng.first, lat_lng.second)         # 35.6895 139.6917
    print(flag_count.first, flag_count.second)   # True 42

Generic Stack

struct Stack[T: AnyType]:
    var _data: List[T]

    fn __init__(inout self):
        self._data = List[T]()

    fn push(inout self, item: T):
        self._data.append(item)

    fn pop(inout self) raises -> T:
        if len(self._data) == 0:
            raise Error("Stack is empty")
        return self._data.pop()

    fn peek(self) raises -> T:
        if len(self._data) == 0:
            raise Error("Stack is empty")
        return self._data[len(self._data) - 1]

    fn size(self) -> Int:
        return len(self._data)

    fn is_empty(self) -> Bool:
        return len(self._data) == 0

fn main() raises:
    var int_stack = Stack[Int]()
    int_stack.push(1)
    int_stack.push(2)
    int_stack.push(3)
    print(int_stack.peek())   # 3
    print(int_stack.pop())    # 3
    print(int_stack.size())   # 2

    var str_stack = Stack[String]()
    str_stack.push("a")
    str_stack.push("b")
    print(str_stack.pop())    # b

Constrained Generics with Traits

Use a trait as the type parameter bound to guarantee the generic function can call specific methods on its argument. Without a bound, the compiler only knows the type is "some type" and cannot allow any method calls.

trait Printable:
    fn to_string(self) -> String: ...

struct Dog(Printable):
    var name: String
    fn __init__(inout self, n: String): self.name = n
    fn to_string(self) -> String: return "Dog: " + self.name

struct Point(Printable):
    var x: Int
    var y: Int
    fn __init__(inout self, x: Int, y: Int): self.x = x; self.y = y
    fn to_string(self) -> String:
        return "Point(" + String(self.x) + "," + String(self.y) + ")"

# T must implement Printable — compiler enforces this
fn display[T: Printable](item: T):
    print(item.to_string())

fn main():
    var d = Dog("Rex")
    var p = Point(3, 7)
    display(d)   # Dog: Rex
    display(p)   # Point(3,7)
Trait bound diagram:

  display[T: Printable](item: T)
         ↑
  "T must implement Printable"
         │
  Compiler checks at call site:
    display(Dog)   → Dog implements Printable? ✓ allowed
    display(Int)   → Int implements Printable? ✗ compile error

Generic min and max

trait Comparable:
    fn __lt__(self, other: Self) -> Bool: ...

fn minimum[T: Comparable](a: T, b: T) -> T:
    return a if a < b else b

fn maximum[T: Comparable](a: T, b: T) -> T:
    return b if a < b else a

fn clamp[T: Comparable](value: T, lo: T, hi: T) -> T:
    return minimum(maximum(value, lo), hi)

Multiple Type Parameters

fn zip_apply[T: AnyType, U: AnyType, V: AnyType](
    a: T,
    b: U,
    func: fn(T, U) -> V
) -> V:
    return func(a, b)

fn main():
    fn concat(s: String, n: Int) -> String:
        return s + String(n)

    var result = zip_apply[String, Int, String]("Score: ", 100, concat)
    print(result)   # Score: 100

Generics vs Overloading

Overloading — write separate functions per type:
  fn describe(x: Int) -> String: return "int " + String(x)
  fn describe(x: Float64) -> String: return "float " + String(x)
  fn describe(x: String) -> String: return "string " + x
  (3 definitions, must update all when logic changes)

Generics — write one function with a type parameter:
  fn describe[T: Stringable](x: T) -> String:
      return str(x)
  (1 definition, works for any Stringable type including future ones)

Use overloading when each type needs different logic.
Use generics when the logic is identical for all types.

Key Takeaways

Generics use type parameters in square brackets to write one function or struct for many types. The compiler generates a separate optimized implementation for each concrete type used. Use AnyType as the bound for maximum flexibility, or a trait name to restrict the parameter to types with specific capabilities. Trait-bounded generics enable method calls on the type parameter. Generics eliminate code duplication while preserving both type safety and performance — the same goals as hand-written per-type code, achieved in a fraction of the lines.

Leave a Comment

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