Skip to content

Match

match compares an expression against patterns, top to bottom, and runs the first one that matches. Like if, it is an expression: the chosen arm is the value of the match. The compiler requires the cases to be exhaustive.

The simplest form matches by literal value — clearer than a chain of if/else if when every branch compares the same expression:

A dispatch table by number and by string; _ is the default case.

11-match-literals.zolo
Playground
// Feature: `match` with literals
// Syntax: `match expr { lit1 => arm1, lit2 => arm2, _ => default }`
// When to use: dispatch table by concrete value
// (numbers, strings, bools). Clearer than a chain of
// `if/else if` when every branch compares the same expression.

let x = 3
let name = match x {
  1 => "one",
  2 => "two",
  3 => "three",
  _ => "other",
}
print("x = {name}")  // x = three

// Match with strings.
let day = "tue"
let lng = match day {
  "mon" => "Monday",
  "tue" => "Tuesday",
  "wed" => "Wednesday",
  "thu" => "Thursday",
  "fri" => "Friday",
  _ => "weekend",
}
print(lng)  // Tuesday

// Match returns a value — it's an expression.
fn http_label(code: int) -> str {
  return match code {
    200 => "OK",
    404 => "Not Found",
    500 => "Server Error",
    _ => "Unknown",
  }
}

print(http_label(200))  // OK
print(http_label(404))  // Not Found
print(http_label(418))  // Unknown

Several values on the same arm use the or pattern (a | b):

a | b => … covers multiple values with a single arm.

13-match-or-patterns.zolo
Playground
// Feature: `match` with or-patterns
// Syntax: `pat1 | pat2 | pat3 => arm`
// When to use: group several cases that share the same
// body. Avoids repeating "=> ..." multiple times.

let day = "Saturday"
let kind = match day {
  "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" => "weekday",
  "Saturday" | "Sunday" => "weekend",
  _ => "unknown",
}
print("{day} is {kind}")  // Saturday is weekend

// Or-patterns with integers.
fn small_prime(n: int) -> bool {
  return match n {
    2 | 3 | 5 | 7 | 11 | 13 => true,
    _ => false,
  }
}

print(small_prime(7))  // true
print(small_prime(8))  // false
print(small_prime(13))  // true

// Combining many cases in one concise expression.
let ch = "u"
let kind2 = match ch {
  "a" | "e" | "i" | "o" | "u" => "vowel",
  _ => "consonant or other",
}
print(kind2)  // vowel

Ranges match contiguous intervals:

1..=5 => … matches any value in the range.

14-match-ranges.zolo
Playground
// Feature: `match` with ranges
// Syntax: `start..=end => arm`  (inclusive)
//         `start..end  => arm`  (exclusive)
// When to use: classify a numeric value into continuous
// bands — age, score, time-of-day window, etc.

let age = 25
let group = match age {
  0..=12 => "child",
  13..=19 => "teenager",
  20..=64 => "adult",
  _ => "senior",
}
print("{age} -> {group}")  // 25 -> adult

// Score / report card.
let score = 85
let grade = match score {
  90..=100 => "A",
  80..=89 => "B",
  70..=79 => "C",
  60..=69 => "D",
  _ => "F",
}
print("{score} = {grade}")  // 85 = B

// Combination of literal + range.
let h = 14
let period = match h {
  0..=5 => "late night",
  6..=11 => "morning",
  12 => "noon",
  13..=17 => "afternoon",
  18..=23 => "evening",
  _ => "?",
}
print("{h}h -> {period}")  // 14h -> afternoon

A guard adds an extra condition to the arm:

x if x > 0 => … only matches when the guard is true.

15-match-guards.zolo
Playground
// Feature: `match` with guards
// Syntax: `pattern if cond => arm`
// When to use: refine a pattern with an arbitrary
// condition. Runs only if the pattern matches AND the
// condition is true. Replaces ranges/literals when the
// rule is dynamic.

let n = -5
let descr = match n {
  x if x < 0 => "negative: {x}",
  0 => "zero",
  x if x < 10 => "small positive: {x}",
  x => "large: {x}",
}
print(descr)  // negative: -5

// Guards using other variables in scope.
let limit = 100
let v = 250
let result = match v {
  x if x <= limit => "ok ({x})",
  x => "overflow ({x})",
}
print(result)  // overflow (250)

// Combining a guard with a range pattern.
let score = 87
let level = match score {
  s if s == 100 => "perfect",
  90..=99 => "excellent",
  s if s >= 70 => "ok ({s})",
  _ => "poor",
}
print(level)  // ok (87)

match also destructures structs and enums, binding the fields:

Extracts struct fields right in the pattern.

16-match-struct.zolo
Playground
// Feature: `match` with struct destructuring
// Syntax: `match s { Type { field1, field2, .. } => ... }`
// When to use: extract fields from a struct while
// branching. `..` ignores extra fields.

struct Point {
  x: float,
  y: float,
  z: float,
}

let p = Point { x: 0.0, y: 0.0, z: 5.0 }

let location = match p {
  Point { x: 0.0, y: 0.0, z: 0.0 } => "origin",
  Point { x, y: 0.0, z: 0.0 } => "X axis at {x}",
  Point { x: 0.0, y, z: 0.0 } => "Y axis at {y}",
  Point { x: 0.0, y: 0.0, z } => "Z axis at {z}",
  Point { x, y, z } => "point ({x},{y},{z})",
}
print(location)  // Z axis at 5

// Pick only the fields you need with `..`.
struct User {
  name: str,
  age: int,
  active: bool,
}

let u = User { name: "Alice", age: 30, active: true }
let label = match u {
  User { name, .. } => "hello {name}",
}
print(label)  // hello Alice

Each enum variant becomes an arm — exhaustive by construction.

17-match-enum.zolo
Playground
// Feature: `match` with enum destructuring
// Syntax: `match val { EnumName::Variant(payload) => ... }`
// When to use: cover every variant of an enum
// (sum-type / tagged union). The compiler verifies that
// every case is handled (exhaustive).

enum Shape {
  Circle(float),
  Rectangle(float, float),
  Triangle(float, float, float),
}

fn area(s: Shape) -> float {
  return match s {
    Shape::Circle(r) => 3.14159 * r * r,
    Shape::Rectangle(w, h) => w * h,
    Shape::Triangle(a, b, c) => {
      // Heron's formula.
      let s = (a + b + c) / 2.0
      return s
    },
  }
}

print(area(Shape.Circle(2.0)))  // ~12.566
print(area(Shape.Rectangle(3.0, 4.0)))  // 12

// Variant without payload + variant with payload in the same enum.
enum Maybe {
  Yes,
  No,
  Custom(str),
}

fn say(m: Maybe) -> str {
  return match m {
    Maybe::Yes => "yes",
    Maybe::No => "no",
    Maybe::Custom(txt) => "maybe: {txt}",
  }
}

print(say(Maybe.Yes))  // yes
print(say(Maybe.No))  // no
print(say(Maybe.Custom("depends")))  // maybe: depends

And patterns can be nested (enum inside enum, arrays):

18-match-nested.zolo
Playground
// Feature: `match` with nested patterns
// Syntax: patterns inside patterns
// When to use: match composite structures (enum inside
// enum, struct inside array, etc.) without doing it
// in two stages.

use std::Result

enum Result {
  Ok(int),
  Err(str),
}

enum Step {
  Start,
  Done(Result),
}

fn descr(s: Step) -> str {
  return match s {
    Step::Start => "starting",
    Step::Done(Result::Ok(n)) => "ok with {n}",
    Step::Done(Result::Err(msg)) => "error: {msg}",
  }
}

print(descr(Step.Start))  // starting
print(descr(Step.Done(Result.Ok(42))))  // ok with 42
print(descr(Step.Done(Result.Err("timeout"))))  // error: timeout

// Nested pattern in arrays.
let pairs = [[1, 2], [0, 0], [3, 4]]
for p in pairs {
  let m = match p {
    [0, 0] => "origin",
    [x, 0] => "X axis at {x}",
    [0, y] => "Y axis at {y}",
    [x, y] => "({x}, {y})",
    _ => "?",
  }
  print(m)
}
// expected:
// (1, 2)
// origin
// (3, 4)

Challenge

In the literals example, replace _ with a range 400..=499 => "client error" and run it. What happens to exhaustiveness?

enespt-br