Language
match
match compares a value with patterns, top to bottom, and runs the first arm that fits:
def enum Day { Mon, Tue, Wed, Thu, Fri, Sat, Sun }
fun kind(d: Day): string {
return match d {
Sat, Sun => "weekend"
else => "weekday"
}
}
fun size(n: int): string {
return match n {
0 => "zero"
1..=9 => "small"
10..100 => "medium"
else => "large"
}
}
print(kind(Day.Sat), size(42))Each arm is pattern => result. Arms are separated by new lines or commas, so a short match fits on one line:
match n { 0 => "none", 1 => "one", else => "many" }.
Patterns
| Pattern | Matches |
|---|---|
3, "stop", true, 2.5 |
an equal value |
LIMIT, Color.Red |
the value of a constant, variable or enum variant |
Red |
an enum variant, when the subject is that enum |
1..10, 1..=9, "a".."n" |
a number or string in the range (.. leaves out the end, ..= includes it) |
null |
null |
is string, is Circle |
a value of that type |
is Circle c |
a value of that type, available as c with that type |
Shape.Rect(w, h) |
a variant with data, binding its fields |
a, b, c |
any of the patterns |
else |
everything else; arms after it can never run, and the compiler warns about them |
Enum variants with data
A variant pattern lists one entry per field, in the order the variant declares them:
def enum Expr {
Num(value: int),
Neg(inner: Expr),
Add(left: Expr, right: Expr)
}
fun eval(e: Expr): int {
return match e {
Expr.Num(n) => n
Expr.Neg(Expr.Num(0)) => 0
Expr.Neg(inner) => -eval(inner)
Expr.Add(l, r) => eval(l) + eval(r)
}
}
print(eval(Expr.Add(Expr.Num(2), Expr.Neg(Expr.Num(5)))))Each entry is one of:
- a new name, which binds the field (
n,inner); _, which skips the field;- a value the field must equal (
0,"x", a constant); - another variant pattern (
Expr.Num(0)), which matches the field's own value.
A name that is already a variable or constant in scope is a value to compare with, not a new binding: with
var limit = 3, the pattern Expr.Num(limit) matches only Expr.Num(3). Listing the wrong number of fields is
an error that shows the variant's fields.
Type patterns
is T matches values of type T. With a name after the type, the arm can use the value as a T:
fun describe(v: any): string {
return match v {
null => "nothing"
is int n if n > 100 => "a big int"
is int => "the int ${v + 1}"
is string s => "a string of ${len(s)}"
is [int] => "some ints"
else => "something else"
}
}
print(describe(500), describe(1), describe("hey"), describe([1]), describe(2.5))The subject itself is also narrowed inside the arm: after is int =>, v is an int there.
The same works for interfaces and struct hierarchies (is Dog d) and for enum variants (is Shape.Circle).
Guards
pattern if condition => ... matches only when the condition is also true. The condition can use the names the
pattern binds:
def enum Shape {
Rect(w: float, h: float),
Circle(r: float)
}
fun name(s: Shape): string {
return match s {
Shape.Rect(w, h) if w == h => "square"
Shape.Rect(_, _) => "rectangle"
Shape.Circle(r) if r == 0.0 => "point"
Shape.Circle(_) => "circle"
}
}
print(name(Shape.Rect(2.0, 2.0)), name(Shape.Circle(1.0)))An arm with a guard does not count as covering its pattern, because the guard can be false.
match without a subject
Without a value after match, every arm is a condition, checked top to bottom, like a chain of if/else if
that produces a value:
fun grade(score: int): string {
return match {
score >= 90 => "A"
score >= 80 => "B"
score >= 70 => "C"
else => "F"
}
}
print(grade(85))Exhaustiveness
A match that produces a value must cover every case: with an else arm, by listing every variant of an enum, or
true and false for a bool. Otherwise it is an error that names what is missing:
error: this `match` produces a value, so it must handle every case
= help: add arms for Color.Blue, or an `else` armThe arms of such a match are expressions. A block (=> { ... }) is an error there; move the work into a function,
or use match as a statement.
match as a statement
A match whose value is not used is a statement. It may leave cases out, and its arms can be blocks:
fun run(command: string) {
match command {
"go", "start" => print("starting")
"stop" => {
print("saving")
print("stopping")
}
}
}
run("go")
run("stop")
run("other")The compiler warns when a statement match on an enum misses variants ("match does not handle C.G, C.B"), and
when an arm can never run because an earlier arm already handles its case ("this case is already handled on line
...").
How it runs
The subject is computed once. The patterns are then checked top to bottom, and the first that matches (with its guard)
runs its arm; later arms are not looked at. Matching on an enum without data compares a number, and a variant with
data checks the variant first and then the fields, so a match costs about as much as the if chain you would
otherwise write.