Mojo Enums

An enum (enumeration) defines a type that holds one value from a fixed set of named options. Instead of using raw integers or strings to represent states like "open", "closed", or "pending", you create an enum that makes the valid choices explicit. Mojo enums are zero-cost at runtime and integrate with the type system for compile-time safety.

The Traffic Light Analogy

Without enum (fragile):
  var light = 0   # 0=red, 1=yellow, 2=green — who remembers?
  if light == 3:  # typo — no error from compiler, but logic breaks

With enum (safe):
  var light = TrafficLight.Red
  if light == TrafficLight.Purple:  # compile error — Purple doesn't exist!

  The compiler enforces that only valid states are used.

Defining an Enum

from enum import Enum

@value
struct Direction(Stringable):
    alias North = 0
    alias South = 1
    alias East  = 2
    alias West  = 3

Mojo currently implements enums using structs with alias constants. Each alias holds an integer value. The struct wrapper gives the set of options a single named type.

Using Enum Values

struct Color:
    alias Red   = 0
    alias Green = 1
    alias Blue  = 2

fn describe_color(c: Int):
    if c == Color.Red:
        print("Red — stop signal")
    elif c == Color.Green:
        print("Green — go signal")
    elif c == Color.Blue:
        print("Blue — calm signal")
    else:
        print("Unknown color")

fn main():
    describe_color(Color.Red)     # Red — stop signal
    describe_color(Color.Blue)    # Blue — calm signal
    describe_color(Color.Green)   # Green — go signal
Memory representation:
  Color.Red   → 0 (Int, zero bytes at runtime — it's a compile alias)
  Color.Green → 1
  Color.Blue  → 2

  The names exist only in source code and compiler.
  The program stores and compares plain integers at runtime.

Enums for State Machines

State machines model systems that move through a fixed set of states. Enums make the valid states explicit and prevent invalid transitions.

struct OrderStatus:
    alias Pending   = 0
    alias Confirmed = 1
    alias Shipped   = 2
    alias Delivered = 3
    alias Cancelled = 4

fn next_status(current: Int) -> Int:
    if current == OrderStatus.Pending:
        return OrderStatus.Confirmed
    elif current == OrderStatus.Confirmed:
        return OrderStatus.Shipped
    elif current == OrderStatus.Shipped:
        return OrderStatus.Delivered
    else:
        return current   # no further transition

fn status_name(s: Int) -> String:
    if s == OrderStatus.Pending:   return "Pending"
    if s == OrderStatus.Confirmed: return "Confirmed"
    if s == OrderStatus.Shipped:   return "Shipped"
    if s == OrderStatus.Delivered: return "Delivered"
    if s == OrderStatus.Cancelled: return "Cancelled"
    return "Unknown"

fn main():
    var order = OrderStatus.Pending
    print(status_name(order))   # Pending

    order = next_status(order)
    print(status_name(order))   # Confirmed

    order = next_status(order)
    print(status_name(order))   # Shipped

    order = next_status(order)
    print(status_name(order))   # Delivered
State machine diagram:
  Pending → Confirmed → Shipped → Delivered
                ↓
            Cancelled  (can jump here from Pending or Confirmed)

Enums with Associated Data (Tagged Union Pattern)

Sometimes an enum variant needs to carry extra data. Use a struct with an enum tag field plus data fields to model this pattern.

struct ShapeKind:
    alias Circle    = 0
    alias Rectangle = 1
    alias Triangle  = 2

struct Shape:
    var kind: Int
    var a: Float64   # radius for circle, width for rect, base for triangle
    var b: Float64   # unused for circle, height for rect, height for triangle

    fn __init__(inout self, kind: Int, a: Float64, b: Float64 = 0.0):
        self.kind = kind
        self.a = a
        self.b = b

    fn area(self) -> Float64:
        if self.kind == ShapeKind.Circle:
            return 3.14159 * self.a * self.a
        elif self.kind == ShapeKind.Rectangle:
            return self.a * self.b
        else:   # Triangle
            return 0.5 * self.a * self.b

fn main():
    var shapes = List[Shape]()
    shapes.append(Shape(ShapeKind.Circle,    5.0))
    shapes.append(Shape(ShapeKind.Rectangle, 4.0, 6.0))
    shapes.append(Shape(ShapeKind.Triangle,  3.0, 8.0))

    for i in range(len(shapes)):
        print("Area:", shapes[i].area())

Output:

Area: 78.53975
Area: 24.0
Area: 12.0

Enum-Driven Configuration

struct LogLevel:
    alias Debug   = 0
    alias Info    = 1
    alias Warning = 2
    alias Error   = 3

var CURRENT_LOG_LEVEL = LogLevel.Info

fn log(level: Int, message: String):
    if level >= CURRENT_LOG_LEVEL:
        if level == LogLevel.Debug:   print("[DEBUG]",   message)
        elif level == LogLevel.Info:  print("[INFO]",    message)
        elif level == LogLevel.Warning: print("[WARN]",  message)
        else:                         print("[ERROR]",   message)

fn main():
    log(LogLevel.Debug,   "Loop iteration 5")     # suppressed (below Info)
    log(LogLevel.Info,    "Server started")       # [INFO] Server started
    log(LogLevel.Warning, "High memory usage")    # [WARN] High memory usage
    log(LogLevel.Error,   "Connection lost")      # [ERROR] Connection lost

Enums vs Raw Constants

Raw constants (error-prone):
  let RED = 0
  let GREEN = 1
  fn paint(color: Int): ...   # accepts ANY int, even invalid ones

Enum struct (safer):
  struct Color:
      alias Red = 0; alias Green = 1
  fn paint(color: Int): ...
  paint(Color.Red)    ✓
  paint(Color.Red + 999)  ← still passes as Int, but naming makes intent clear

  Best practice: use the alias name, never the raw integer, at every call site.

Key Takeaways

Mojo enums use structs with alias constants to define a named set of options. Each alias maps to an integer value with zero runtime overhead. Enums make valid states explicit, replace magic numbers with readable names, and prevent typos that would otherwise silently use wrong values. Use enums for status codes, configuration flags, state machines, and direction sets. The status-name helper pattern — a function that maps enum integers to display strings — keeps display logic centralized and consistent.

Leave a Comment

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