Skip to content

07 · Pattern Matching

Patterns in Statements: if let, while let, let else and match-expression

Beyond `match`, patterns appear in `if let`, `while let`, `let else` and as expressions that return a value.

On this page

Patterns in Zolo are not restricted to match. They appear in four other syntactic forms, each with a distinct purpose.

if let: matching a single variant

When you only care about one enum variant (typically Some/Just/Ok), if let is more concise than a two-arm match. The else is optional.

if let with and without else; chaining if let as variant dispatch.

13-if-let.zolo
Playground
// Feature: `if let` — match a single variant
// Syntax: `if let Pat = expr { ... } else { ... }`
// When to use: you only care about ONE of the enum's variants
// (typically `Some`/`Just`/`Ok`) and want a simple else for the
// rest. More concise than a two-arm `match`.

enum Opt {
  Some(int),
  None,
}

let val = Opt::Some(42)
if let Opt::Some(x) = val {
  print("got: {x}")  // got: 42
} else {
  print("none")
}

// No else — only runs if matched.
let empty = Opt::None
if let Opt::Some(x) = empty {
  print("got: {x}")
}
print("after if let")  // after if let

// Chained `if let` for different variants.
enum Shape {
  Circle(float),
  Square(float),
  Empty,
}

fn area(s: Shape) -> float {
  if let Shape::Circle(r) = s {
    return 3.14159 * r * r
  }
  if let Shape::Square(side) = s {
    return side * side
  }
  return 0.0
}

print(area(Shape::Circle(5.0)))  // 78.53975
print(area(Shape::Square(4.0)))  // 16
print(area(Shape::Empty))  // 0

while let: pattern-driven loop

while let repeats while the pattern matches and stops when it fails. It is the natural idiom for consuming a stack, queue or iterator that signals end with None/Nothing.

Note: while let has partial support in the type checker in some configurations. The example uses the equivalent form loop { match ... } which is safe in all cases.

Stack draining with loop { match ... } — equivalent to while let.

14-while-let.zolo
Playground
// Feature: `while let` — loop while a pattern matches

// Syntax: `while let Pat = expr { ... }`

// When to use: consume an iterator / pop a stack / drain a queue —

// anything that returns `Some`/`Just` while items remain and

// `None`/`Nothing` once empty.

//

// The matched payload is bound with its precise type inside the loop body.


use std::Array

enum Opt {
  Some(int),
  None,
}

// Manual stack with pop. Array slicing via `arr[a..b]` is supported

// (returns a new array). `Array.pop` is used here for in-place removal.

struct MyStack {
  items: [int],
}

impl MyStack {
  fn from(items: [int]) -> MyStack {
    return MyStack { items: items }
  }

  fn take(self) -> Opt {
    if self.items.len() == 0 {
      return Opt::None
    }
    let last = self.items.pop() ?? 0
    return Opt::Some(last)
  }
}

let s = MyStack::from([1, 2, 3, 4, 5])

while let Opt::Some(item) = s.take() {
  print(item)
}
print("empty")
// expected:

// 5

// 4

// 3

// 2

// 1

// empty

let else: guard clause with binding

let else is the idiom for extracting a value you expect to be there, with a clean exit otherwise. The else block must diverge (return, panic, break). After the line, the names from the pattern are available in the normal scope — with no extra indentation.

let else with enum, with array pattern and chained as clause guards.

15-let-else.zolo
Playground
// Feature: `let else` — infallible destructuring with diverging fallback
// Syntax: `let Pat = expr else { ... return / break / panic }`
// When to use: extract a value you EXPECT to match, with a clean
// escape hatch when it doesn't. The `else` block must end the
// function (return/panic/etc.). After the line, the pattern's
// names are available in normal scope.

enum Opt {
  Some(int),
  None,
}

fn double_or_zero(o: Opt) -> int {
  let Opt::Some(x) = o else {
    return 0
  }
  // `x` is available from here on.
  return x * 2
}

print(double_or_zero(Opt::Some(21)))  // 42
print(double_or_zero(Opt::None))  // 0

// let-else with array pattern: take the first or return -1.
fn first(arr: [int]) -> int {
  let [head, ..rest] = arr else {
    return -1
  }
  return head
}

print(first([10, 20, 30]))  // 10
let empty: [int] = []
print(first(empty))  // -1

// Chaining: several guard clauses in a row.
fn add_first_two(arr: [int]) -> int {
  let [a, ..rest] = arr else { return -1 }
  let [b, ..rest2] = rest else { return -1 }
  return a + b
}

print(add_first_two([3, 4, 5]))  // 7
print(add_first_two([3]))  // -1

match as an expression

match is an expression: the value of the chosen arm is the value of the entire match. It can appear on the right-hand side of a let, inside an arithmetic expression, in string interpolation or as a function body.

match as the RHS of let, inside an arithmetic expression and as a function return.

16-match-as-expression.zolo
Playground
// Feature: `match` as expression — returns a value
// Syntax: `let v = match expr { pat => val, ... }`
// When to use: a match's value is the value of the matched arm. It
// replaces nested ifs when you only need a value derived from a
// classification.

let n = 7

// Can be the RHS of a let.
let parity = match n % 2 {
  0 => "even",
  _ => "odd",
}
print(parity)  // odd

// Can be part of a larger expression.
let pts = 50 + match n {
  n if n > 5 => 100,
  n if n > 0 => 50,
  _ => 0,
}
print(pts)  // 150

// Can appear inside interpolation.
let label = match n {
  0 => "zero",
  1..=5 => "small",
  _ => "big",
}
print("n={n} -> {label}")  // n=7 -> big

// `match` as a function return — no `return` needed for a terminal
// match.
fn classify(x: int) -> str {
  return match x {
    n if n < 0 => "negative",
    0 => "zero",
    _ => "positive",
  }
}

print(classify(-3))  // negative
print(classify(0))  // zero
print(classify(42))  // positive
// expected:
// odd
// 150
// n=7 -> big
// negative
// zero
// positive

Challenge

Rewrite the classify function from the last example using if let instead of match. Which version do you find more readable for that case?

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

enespt-br