A pattern describes what a value looks like, and can give names to parts of it. For example,
the pattern Ok(value) matches any Ok tag, and gives the name value to its payload. It
doesn't match an Err tag.
Patterns show up in four places:
- The branches of a
match expression - The left side of an
= assignment - Function arguments, like the
{ x, y } in |{ x, y }| x + y - The part of a
for loop between for and in
match
A match expression evaluates a value, then goes through its branches from top to bottom,
looking for the first one whose pattern matches the value. Then it evaluates that branch's body,
and that's what the whole match expression evaluates to:
describe = |result|
match result {
Ok(value) => "success: ${value}"
Err(_) => "error"
}
The value after match only gets evaluated once. All the branches' patterns have to work on the
same type (the type of the value being matched), and all the branches' bodies have to evaluate to
the same type (the type of the whole match).
Names that a pattern introduces, like value above, can only be used in that branch.
Since the first matching branch wins, the order of branches matters when more than one could
match. Here, 0 matches the first branch, not the second:
classify = |number|
match number {
0 => "zero"
_ => "another number"
}
Branch Alternatives
You can use | to give several patterns the same body:
is_primary = |color|
match color {
Red | Green | Blue => True
Yellow | Orange | Purple => False
}
This works the same way as writing out a separate branch for each pattern, each with the same body.
If the alternatives give names to things, they all have to give the same names, with the same
types. Otherwise the body might refer to a name that the matching alternative didn't provide.
if Guards on Branches
A branch can have an if after its pattern, which is called a guard:
describe = |numbers|
match numbers {
[first, .. as rest] if first > 0 => PositiveStart(first, rest)
_ => Other
}
The guard only runs if the pattern matched, so it can use names from the pattern (like first
here). If the guard is False, the branch doesn't count as matching after all, and match moves
on to the next branch.
For exhaustiveness purposes, the compiler doesn't count a guarded branch as
covering anything, since its guard might be False. So even _ if condition => … needs another
branch after it.
Names and _
A lowercase name matches anything, and gives that name to the value:
identity = |value|
match value {
anything => anything
}
_ also matches anything, but doesn't give it a name. A name that starts with an underscore, like
_ignored, does give the value a name, but tells the compiler (and anyone reading the code) that
you don't plan to use it, so you don't get an unused name warning.
Literals
Number and string literals match values that are equal to them:
describe = |value|
match value {
0 => "zero"
1 => "one"
_ => "many"
}
Since numbers and strings have way too many possible values to list them all, matching on them
almost always needs a _ branch at the end.
Number literal patterns can have type suffixes, just like number literals
in expressions:
classify : F32 -> Str
classify = |number|
match number {
1.5.F32 => "one and a half"
_ => "another number"
}
So can single-quoted characters, which are number literals too:
is_ascii_a = |byte|
match byte {
'A'.U8 => True
_ => False
}
True and False are tags, so they're matched the same way as any other tag.
String Patterns with Captures
A string pattern can contain ${…} with a name inside, which matches any text in that position and
gives it that name:
user_id = |path|
match path {
"users/${id}" => Some(id)
_ => None
}
Here, "users/42" matches, with id being "42". "posts/42" doesn't match, because it doesn't
start with "users/".
When there's more text after a capture, the capture stops at the first place that text appears.
For example, matching "a/b/c.txt" against "${dir}/${file}.txt" gives dir the value "a" and
file the value "b/c".
You can write ${_} to match some text without naming it. Two captures can't be right next to
each other, as in "${a}${b}", because there would be no way to tell where a should stop and b
should start.
A tag pattern matches a particular tag, and has a pattern for each of its payloads:
unwrap_or = |result, fallback|
match result {
Ok(value) => value
Err(_) => fallback
}
The payload patterns can be any kind of pattern, including other tags. For example,
Ok(Pair(left, right)) matches an Ok whose payload is a Pair tag, and names the Pair's two
payloads.
Records
A record pattern names a record's fields:
full_name = |person| {
{ first, last } = person
"${first} ${last}"
}
You can use : to give a field's value a different name, or to match it against another pattern:
name = |person|
match person {
{ name: displayed_name, active: True } => Some(displayed_name)
{ active: False, .. } => None
}
A record pattern only matches records with exactly the fields it lists, unless it ends in ..,
which means "and any other fields." If you write a name after the .., that name gets a record
containing all the other fields:
remove_name = |person|
match person {
{ name, ..rest } => Pair(name, rest)
}
answer = remove_name({ name: "Sam", age: 32 }) # Pair("Sam", { age: 32 })
Tuples
A tuple pattern has a pattern for each element:
swap = |pair| {
(first, second) = pair
(second, first)
}
Like tuples themselves, tuple patterns have at least two elements. See
Destructuring Tuples.
Lists
A list pattern matches lists of a particular length:
describe = |items|
match items {
[] => Empty
[only] => One(only)
[first, second] => Two(first, second)
[_, _, ..] => Many
}
.. matches any number of elements (including zero), so [_, _, ..] matches any list with at
least two elements. A list pattern can have one .., and it can be at the beginning, the middle,
or the end:
ends = |items|
match items {
[first, .., last] => Some(Pair(first, last))
_ => None
}
(Note that [first, .., last] doesn't match a list with only one element, since first and last
are two separate elements.)
You can write .. as name to give a name to the elements that .. matched, as a list:
split_first = |items|
match items {
[] => None
[first, .. as rest] => Some(Pair(first, rest))
}
Nested Patterns and as
Patterns can go inside other patterns, the same way values can go inside other values:
get_name = |response|
match response {
Ok({ user: { name } }) => Some(name)
Err(_) => None
}
You can write as and a name after a pattern, to give a name to the whole value in addition to
its parts:
keep_point = |value|
match value {
(x, y) as point => { x, y, point }
}
Nominal Types
To match a nominal type's backing value, you write the type's name, then .,
then a pattern in parentheses:
Distance := U64
unwrap : Distance -> U64
unwrap = |distance|
match distance {
Distance.(meters) => meters
}
For nominal records, you can leave off the parentheses:
Point := { x : U64, y : U64 }
sum : Point -> U64
sum = |Point.{ x, y }| x + y
(Point.({ x, y }) also works, but Point.{ x, y } is the usual way to write it.) For nominal tag
unions, you can match the tags directly, as in Red => or Color.Red =>. See
Qualified Tags.
Exhaustiveness
A match has to handle every possible value. If it doesn't, you get a compile-time error that
shows some example values that aren't handled.
For example, this match is exhaustive (it handles every possible value), because a Try is
always either Ok or Err:
is_ok = |result|
match result {
Ok(_) => True
Err(_) => False
}
The compiler also tells you about branches that can never match, because every value they'd
match was already handled by earlier branches. Here, the Ok(_) branch can never be reached:
match result {
_ => "handled"
Ok(_) => "unreachable" # This gives a warning.
}
Catch-all Patterns (_)
A catch-all pattern matches every value. _ is the most common one. A plain name, like other,
is also a catch-all; the difference is that other gives the value a name you can use in the
branch's body.
Since a catch-all matches everything, any branch after it can never match, which is why catch-all
branches go last.
Destructuring
Patterns outside match (in assignments, function arguments, and for loops) don't have other
branches to fall back on. So they have to match every possible value of their type. If one
doesn't, you get a compile-time error rather than a runtime crash.
Destructuring Assignments (with =)
The left side of an = can be a pattern:
coordinates = |point| {
{ x, y } = point
(x, y)
}
This is useful for records, tuples, nominal types, and tag unions that only have one tag. It
doesn't work with patterns that might not match. For example, Ok(value) = result gives an error
if result could be an Err; use a match instead, so you can say what to do in that case.
(If the error type is the empty tag union, as in Try(U64, []), then result
can't be an Err, so Ok(value) = result is allowed.)
Function arguments are patterns too:
sum_point = |{ x, y }| x + y
So is the part of a for loop between for and in:
var $total = 0
for (key, value) in [(1, 2), (3, 4)] {
$total = $total + key + value
}
If different elements need different handling, use a match inside the loop.
Matching on a value never allocates memory or copies the value. The compiler turns a whole match
into a single decision tree that checks each part of
the value at most once, so having lots of branches doesn't mean checking the same thing over and
over. Some details for specific kinds of patterns:
- Tags compile to a check of the tag union's discriminant, which is a small number. Matching on many tags can compile to a single jump to the right branch, like a
switch statement in C. - Records and tuples cost nothing to destructure. Their fields are at known offsets, so naming a field is just reading it.
- Lists check the list's length first, then the elements the pattern needs. Naming the rest of a list with
.. as rest doesn't copy any elements; rest refers to part of the original list's memory (and keeps that memory alive until rest is no longer used). - Strings compare bytes. A string pattern with captures has to search for the text after each capture, which takes time proportional to the length of the string being matched.