Data & Control

Patterns & Control Flow

Select and bind data using patterns alongside if, match, and for control flow.

Use if for a single yes-or-no condition, match when the shape or category of a value determines the result, and for when performing the same work on each item. Patterns are constructs inside match and for that test data and bind names to required parts.

Run one example

Save this as data-control.tpz:

TOPAZ
record StudyTask {
    title: string,
    minutes: int,
    done: bool = false,
}

function pace(task: StudyTask) -> string {
    if task.done {
        return "done"
    }
    match task.minutes {
        case 1..15 => "quick"
        case 16..30 => "focused"
        case _ => "deep"
    }
}

let tasks: Array<StudyTask> = [
    StudyTask { title: "Run first program", minutes: 10, done: true },
    StudyTask { title: "Build application", minutes: 25 },
]
let mut remaining = 0
for task in tasks {
    if !task.done {
        remaining = remaining + task.minutes
    }
    print("{task.title}: {pace(task)}")
}
print("remaining: {remaining} minutes")

Check it before running it:

BASH
topaz check data-control.tpz
topaz run data-control.tpz

The output follows array order:

Output
Run first program: done
Build application: focused
remaining: 25 minutes

Follow the decisions

The loop binds each array element to task. The inner if evaluates a single boolean condition to check whether the task is unfinished. It neither classifies multiple cases nor destructures the value.

When both outcomes require handling, write if condition { ... } else { ... }. Compatible branch values allow the entire if expression to yield a value.

pace first uses if for the completed task case. Its match expression then evaluates task.minutes once and tests the case arms from top to bottom. 1..15 and 16..30 are range patterns. _ serves as the catch-all pattern, ensuring every other integer produces a result.

ChooseWhen it fitsWhat it produces
ifA single condition selects between branchesA value when compatible branches yield values, or an effect-only statement otherwise
matchA value has several meaningful shapes or categoriesThe selected arm's value, along with any pattern bindings in that arm
forEvery item must be visited in iteration orderStatements executing side effects, or an Array of body results when used as an expression

Patterns select and bind

A literal or range pattern tests a value. An identifier such as task binds the matched value. Constructor patterns such as Some(value) and Err(message) destructure Option and Result values. Record patterns extract named fields.

Array patterns use .. rather than call spread syntax. case [head, ..tail] => binds the first element and assigns the remaining elements to tail as an Array. Use _ when a fallback is required but the value is not needed.

if let pattern = value { ... } is useful for matching a single successful shape with an optional fallback. while let repeats the test until pattern matching fails. Their bindings are immutable and scoped exclusively to the successful block.

Common mistake

Do not write [head, ...tail] inside a pattern. The ... syntax is reserved for array and call spreads. The standard pattern for capturing remaining elements is [head, ..tail].

Enum construction and enum pattern matching are intentionally distinct. Construct variants using the qualified enum name, such as Status.Paused("review"), but match variants without qualification, such as case Paused(reason) =>.

Exact behavior that affects a choice

  • A value-producing if or match requires compatible branch results.
  • A closed enum match is verified for complete variant coverage. A guarded arm does not count toward that coverage.
  • When the checker cannot verify that a match is complete, use an explicit _ arm.
  • Patterns evaluate from left to right. Bindings from a failed alternative do not leak into subsequent alternatives.
  • A refutable pattern failure inside a collection comprehension produces an error rather than filtering items. Add an explicit comprehension if clause to skip items.
  • Array, integer ranges, Set, and map.keys have a defined iteration order. Strings are not directly iterable.
  • while loops and statement-position for loops produce no value. A for loop evaluated in expression position collects its body results in source order.

User-defined patterns, generators, user-defined iteration, exception-handling control structures, and labels on while or for loops are not supported in the current language.

Continue from the course example in Data and Control. See Records & Nominal Data for the shapes patterns can destructure, and Collections & Comprehensions for iteration and filtering.