Enums
An enum defines a type that can be one of several variants. Matching
on an enum is exhaustive by construction — the compiler ensures every case
is covered.
Unit variants
The simplest form: variants with no data, used for states, modes, or flags:
Color and Status — variants with no payload; match as a dispatch table.
// Feature: Unit enums — variants without data
// Syntax: `enum Name { A, B, C }` — each variant is a marker.
// When to use: represent a closed set of states/options
// that carry no data — flags, modes, statuses.
// Definition.
enum Color {
Red,
Green,
Blue,
}
// Construct a variant: `Color::Variant`.
let c = Color::Red
// Match all variants.
match c {
Color::Red => print("red"),
Color::Green => print("green"),
Color::Blue => print("blue"),
}
// Function that dispatches by variant.
fn css_hex(c: Color) -> str {
return match c {
Color::Red => "#ff0000",
Color::Green => "#00ff00",
Color::Blue => "#0000ff",
}
}
print(css_hex(Color::Red)) // #ff0000
print(css_hex(Color::Green)) // #00ff00
print(css_hex(Color::Blue)) // #0000ff
// Enum as a finite "state".
enum Status {
Idle,
Running,
Done,
Failed,
}
fn describe(s: Status) -> str {
return match s {
Status::Idle => "waiting",
Status::Running => "running",
Status::Done => "completed",
Status::Failed => "failed",
}
}
let states = [Status::Idle, Status::Running, Status::Done, Status::Failed]
for s in states {
print(describe(s))
}
// expected:
// waiting
// running
// completed
// failed
Variants with positional data
Each variant can carry values, making the enum a sum type (ADT):
Shape::Circle(r), Rect(w, h) — binding fields in the pattern; if let for a single case.
// Feature: Enums with tuple variants — variants carry positional data
// Syntax: `enum X { Var(T1, T2, ...) }` — variants are tuple "constructors".
// When to use: union types, expressing that the value is "one of these shapes",
// extracting the data via `match`.
// Shape — each variant carries its own data.
enum Shape {
Circle(float),
// radius
Rect(float, float),
// width, height
Triangle(float, float, float),
}
// 3 sides
// Dispatch by variant and bind the fields.
fn describe(s: Shape) -> str {
return match s {
Shape::Circle(r) => "circle r={r}",
Shape::Rect(w, h) => "rect {w}x{h}",
Shape::Triangle(a, b, c) => "tri {a},{b},{c}",
}
}
print(describe(Shape::Circle(5.0)))
print(describe(Shape::Rect(4.0, 6.0)))
print(describe(Shape::Triangle(3.0, 4.0, 5.0)))
// Compute approximate area.
fn area(s: Shape) -> float {
return match s {
Shape::Circle(r) => 3.14159 * r * r,
Shape::Rect(w, h) => w * h,
Shape::Triangle(a, b, c) => {
// Approximation: semi-perimeter as a proxy
let p = a + b + c
return p / 2.0
},
}
}
print(area(Shape::Circle(2.0))) // ~12.566
print(area(Shape::Rect(3.0, 4.0))) // 12
// `if let` for the case where you already know the variant.
let c = Shape::Circle(7.0)
if let Shape::Circle(r) = c {
print("circle radius={r}") // circle radius=7
}
// Result-like enum (one-tuple).
enum Parsed {
Ok(int),
Err(str),
}
fn parse_pos(s: str) -> Parsed {
if s == "1" { return Parsed::Ok(1) }
if s == "2" { return Parsed::Ok(2) }
return Parsed::Err("not a number")
}
let r = parse_pos("1")
match r {
Parsed::Ok(n) => print("got {n}"),
Parsed::Err(msg) => print("error: {msg}"),
}
Variants with named fields
When a variant has 3+ fields or names make the code clearer, use struct-like syntax inside the enum:
Event::Click { x, y } — UI events and commands with named fields.
// Feature: Enums with struct variants — variants with NAMED fields
// Syntax: `enum X { Var { field: T, ... } }`
// When to use: variants with 3+ fields where position is not
// obvious; events, messages, commands.
// UI events — each event has specific fields.
enum Event {
Click {
x: int,
y: int,
},
KeyPress(str),
// tuple-style
Resize {
width: int,
height: int,
},
Quit,
}
// unit-style
let ev = Event::Click { x: 100, y: 200 }
match ev {
Event::Click { x, y } => print("click ({x}, {y})"),
Event::KeyPress(key) => print("key: {key}"),
Event::Resize { width, height } => print("resize {width}x{height}"),
Event::Quit => print("quit"),
}
// Multiple events.
let events = [
Event::Click { x: 10, y: 20 },
Event::KeyPress("Enter"),
Event::Resize { width: 800, height: 600 },
Event::Quit,
]
for e in events {
match e {
Event::Click { x, y } => print("click ({x},{y})"),
Event::KeyPress(k) => print("press {k}"),
Event::Resize { width, height } => print("resize {width}x{height}"),
Event::Quit => print("quit"),
}
}
// expected:
// click (10,20)
// press Enter
// resize 800x600
// quit
// Command enum — classic example for an AST.
enum Command {
Move {
dx: int,
dy: int,
},
Set {
name: str,
value: int,
},
Print {
msg: str,
},
}
fn run(cmd: Command) {
match cmd {
Command::Move { dx, dy } => print("moved {dx},{dy}"),
Command::Set { name, value } => print("set {name} = {value}"),
Command::Print { msg } => print("[LOG] {msg}"),
}
}
run(Command::Move { dx: 3, dy: -2 })
run(Command::Set { name: "ttl", value: 30 })
run(Command::Print { msg: "ok" })
Generic enums
Type parameters make the enum reusable — the classic patterns of
Maybe<T>, Result<T, E>, and Either<L, R>:
Maybe<T>, Result<T, E>, Either<L, R> — parameterised algebraic types.
// Feature: Generic enums — type-parameterized variants
// Syntax: `enum X<T, U> { Var(T), Other(U) }`
// When to use: reusable algebraic types — `Option<T>`,
// `Result<T, E>`, `Either<L, R>`.
// Simple Maybe — analogous to Option/Maybe.
enum Maybe<T> {
Just(T),
Nothing,
}
fn unwrap_or_zero(m: Maybe<int>) -> int {
return match m {
Maybe::Just(v) => v,
Maybe::Nothing => 0,
}
}
print(unwrap_or_zero(Maybe::Just(42))) // 42
print(unwrap_or_zero(Maybe::Nothing)) // 0
// Result with two type parameters.
enum Result<T, E> {
Ok(T),
Err(E),
}
fn parse_int(s: str) -> Result<int, str> {
if s == "1" { return Result::Ok(1) }
if s == "42" { return Result::Ok(42) }
return Result::Err("invalid: {s}")
}
let r1 = parse_int("42")
let r2 = parse_int("xx")
match r1 {
Result::Ok(n) => print("ok = {n}"),
Result::Err(e) => print("err = {e}"),
}
match r2 {
Result::Ok(n) => print("ok = {n}"),
Result::Err(e) => print("err = {e}"),
}
// expected:
// ok = 42
// err = invalid: xx
// Either — choice between two types.
enum Either<L, R> {
Left(L),
Right(R),
}
fn show(e: Either<str, int>) -> str {
return match e {
Either::Left(s) => "left: {s}",
Either::Right(n) => "right: {n}",
}
}
print(show(Either::Left("hi"))) // left: hi
print(show(Either::Right(7))) // right: 7
Challenge
Define an enum Tree<T> with variants Leaf(T) and Node(T, T). Write
a function sum(t: Tree<int>) -> int that returns the sum of the values.