Match expression
The conditional ? : chooses between two values; Ternary showed why that is better than declaring a var and assigning it in an if. match can do the same with any number of cases. Each arm produces a value instead of running a statement, and the whole match becomes the value of the arm that fit.
The long way: match as a statement
As a statement, choosing a value needs a var, declared first with a placeholder and assigned in every arm:
let day: int32 = 6;
var kindTheLongWay = "";
match day {
6 => {
kindTheLongWay = "weekend";
},
7 => {
kindTheLongWay = "weekend";
},
else => {
kindTheLongWay = "weekday";
}
}
It works, but the name is repeated in every arm, nothing stops an arm from forgetting to assign it, and the result is a var that anything later could change.
The short way: match as a value
As an expression, each arm is just the value it produces:
let kind = match day {
6 => "weekend",
7 => "weekend",
else => "weekday"
};
The binding is a let that gets its value where it is declared. Note the semicolon after the closing brace: the match is part of the let statement, and the statement needs its ;.
A value goes anywhere
Like any value, a match expression can be passed straight to a call. Most months have 31 days, so else covers the common case and the arms list the exceptions:
PrintLine("month {} has {} days", month, match month {
2 => 28,
4 => 30,
6 => 30,
9 => 30,
11 => 30,
else => 31
});
Or it can stand inside arithmetic — here, a price per cup size, multiplied by the number of cups:
let total = cups * match size {
1 => 3,
2 => 4,
else => 5
};
One type, every value covered
A match expression has to produce exactly one value, of one type, whatever the input. That gives the compiler two things to check that a match statement does not need:
flowchart LR
m(["let x = match value { … }"]) --> t{"Do all arms produce<br/>the same type?"}
t -- "no" --> e1["error: match arm<br/>type mismatch"]
t -- "yes" --> c{"Do the arms cover<br/>every possible value?"}
c -- "no" --> e2["error: match on 'int32'<br/>is not exhaustive"]
c -- "yes" --> ok["x gets the value<br/>of the arm that fits"]A match over numbers cannot name every value, so it needs an else arm, and the compiler checks that it is there. The statement-form caveat from Match — an integer that fits no arm silently does nothing — cannot happen here, because "nothing" is not a value.
A bool match that names both true and false already covers every input, so it needs no else:
let sign = match open {
true => "come in",
false => "closed"
};
| Form | Arms produce | Must cover every value? | Result |
|---|---|---|---|
match statement | statements | no — unmatched does nothing | nothing |
match expression | values of one type | yes — else for numbers | one value |
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A `match` is also an expression: each arm can produce a value instead of running a statement,
// and the whole match becomes the value of the arm that fit. So it can initialize a `let`, be
// passed to a call, or appear in arithmetic, just like the `? :` conditional, but with any number
// of cases.
//
// Being one value, it has one type, so every arm must produce the same type:
//
// let label = match day { 6 => "weekend", else => 0 };
// error: match arm type mismatch: expected 'char8[..]', found 'int'
//
// And a value has to come out whatever the input, so the arms must cover every value. A match
// over numbers cannot name them all, so it needs an `else` arm, and the compiler checks:
//
// let kind = match day { 6 => "weekend", 7 => "weekend" };
// error: match on 'int32' is not exhaustive; its arms do not cover every value
// help: add an 'else' arm
//
// A `bool` match that names both `true` and `false` already covers every input.
import Io::PrintLine;
func Main() -> int {
// As a statement, choosing a value needs a `var`, declared first and assigned in every arm.
let day: int32 = 6;
var kindTheLongWay = "";
match day {
6 => {
kindTheLongWay = "weekend";
},
7 => {
kindTheLongWay = "weekend";
},
else => {
kindTheLongWay = "weekday";
}
}
// As an expression, the binding is a `let` that gets its value where it is declared. The
// match ends with a semicolon here, because it is part of the `let` statement.
let kind = match day {
6 => "weekend",
7 => "weekend",
else => "weekday"
};
PrintLine("day {} is a {} ({} the long way)", day, kind, kindTheLongWay);
// Passed straight to a call. Most months have 31 days, so `else` covers the common case.
let month: int32 = 4;
PrintLine("month {} has {} days", month, match month {
2 => 28,
4 => 30,
6 => 30,
9 => 30,
11 => 30,
else => 31
});
// Used inside arithmetic: a price per size, times a quantity.
let size: int32 = 2;
let cups: int32 = 3;
let total = cups * match size {
1 => 3,
2 => 4,
else => 5
};
PrintLine("{} cups of size {} cost {}", cups, size, total);
// A `bool` match that names both values covers every input, so it needs no `else`.
let open = true;
let sign = match open {
true => "come in",
false => "closed"
};
PrintLine("the sign says {}", sign);
return 0;
}
Run it
cd Examples/ControlFlow/MatchExpression
rux run
day 6 is a weekend (weekend the long way)
month 4 has 30 days
3 cups of size 2 cost 12
the sign says come in
Common mistakes
let label = match day { 6 => "weekend", else => 0 }; fails with error: match arm type mismatch: expected 'char8[..]', found 'int'. Every arm must produce the same type.else arm.let kind = match day { 6 => "weekend", 7 => "weekend" }; fails with error: match on 'int32' is not exhaustive; its arms do not cover every value, and the help says to add an else arm.A match that initialises a
let ends with };. Leave out the ; and the compiler stops at the next statement: error: expected ';' after the binding declaration before 'PrintLine'.Try it yourself
- Write
let name = match day { … };that gives the name of each day from 1 to 7, with"unknown"for everything else. - Make the month match return 29 for February in a leap year, using a
boolleapand the conditional? :inside the arm. - Turn the cups example into a statement match that assigns a
var, and compare the two versions. - Remove the
elsearm from the month match and read the error.
Learn more
matchin the Rux Reference- Exhaustive — how the compiler decides whether a match covers every case
- Variant match — matching the cases of your own types