Skip to content

Match Statements

match lets you compare a value against several patterns and run the first arm that fits.

Calibre also has fn match, which is a convenient way to define a function whose body is entirely pattern matching.

let classify := fn match int -> str {
0 => "zero",
1 => "one",
_ => "many"
};

An ordinary function and a fn match function can often express the same idea however the fn match form is usually shorter and keeps the focus on the patterns themselves.

const classify1 := fn (x : int) -> str => match x {
0 => "zero",
_ => "many"
};
const classify2 := fn match int -> str {
0 => "zero",
_ => "many"
};

Each arm can match a different pattern shape, and _ is the fallback wildcard.

You can match simple literal values directly.

match 50 {
10 => print("ten"),
50 => print("fifty"),
_ => print("other")
};

Multiple alternatives can be combined with |.

match 30 {
10 | 20 | 30 => print("small"),
_ => print("other")
};

Ranges can be matched with in.

let classify := fn match int -> str {
in 0..20 => "in-range",
_ => "other"
};

You can also match membership in a literal list.

let classify := fn match int -> str {
in [30, 40, 50] => "in-list",
_ => "other"
};

@ bindings let you capture part of what matched and is arms match based on type.

Here, is int says the arm expects an int, and v @ ... binds the matched value so it can also be used in a guard like if v > 100.

let classify := fn match int -> str {
v @ is int if v > 100 => "large-int",
_ => "other"
};

The if part of an arm is a guard. A guard adds an extra boolean condition after the pattern itself has already matched.

let classify := fn match int -> str {
in 0..20 if true => "small-range",
let v if v > 100 => "large-int",
_ => "other"
};

let arms bind whatever value matched to a name.

This not usually required and can usually be inferred but if a variable with the same name you want to use exists in the scope, it can cause it to be treated as a comparison instead. Therefore let, mut or const can be used to force shadowing and these keywords can also be used within destructuring for the same purpose.

match 50 {
let value => print(value),
_ => {}
};

Tuples can be matched position by position.

match 10, 90, 20 {
10, mut value, 20 => {
value += 90;
print(value);
},
10, const value, _ => print(value),
_ => {}
};

The .. pattern can ignore large parts of a tuple or list unlike _ which only ignores a single value.

match 10, 90, 20 {
10, .., 20 => print("matched"),
_ => {}
};

Structs can be destructured directly in a match arm.

type Pair := struct { left right : int };
match Pair { left : 30, right : 50 } {
{left : 50, right} => print(50 - right),
{left, right} => print(left + right),
_ => {}
};

Enums can be matched by variant, and their payloads can be destructured at the same time.

type PairEnum := enum { Normal : Pair, Tuple : <int, int> };
match PairEnum.Tuple : (10, 30) {
.Normal : {left: l, right: r} => print(l + r),
.Tuple : (val, 20) => print(val),
_ => {}
};

Lists can be matched by exact shape or by prefix.

let classify_list := fn match list:<int> -> str {
[] => "Empty list",
[1] => "List of just 1",
[4, ..] => "List starting with 4",
[_, _] => "List of 2 elements",
_ => "Some other list"
};

Strings can also be matched against to test against how a string input starts and ends easily.

let parse_command := fn match str -> str {
"go:" & direction => "move " & direction,
head @ "noop" & _ => "saw " & head,
_ => "unknown"
};

match can also be used without an explicit value, where each arm acts like a checked condition.

let age := "10";
match {
age := "10" => print("ten"),
_ => print("other")
};