Skip to content

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.

06-enums-unit.zolo
Playground
// 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.

07-enums-tuple.zolo
Playground
// 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.

08-enums-struct.zolo
Playground
// 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.

09-enums-generic.zolo
Playground
// 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.

See also

enespt-br