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.
