match
match compares a value, the subject, against a list of patterns and runs the first arm whose pattern fits. It is both a statement and an expression.
match = "match" subject "{" arm { "," arm } "}"
arm = pattern "=>" body
| "else" "=>" body
body = expression | block | "return" [expression] | "fail" expression
| "break" [label] | "continue" [label]
match status {
200 => PrintLine("OK"),
404 => PrintLine("Not Found"),
else => PrintLine("Unknown")
}
Arms
Each arm is a pattern, =>, and a body. Arms are separated by commas, block arms included, and the last arm takes no trailing comma:
error: trailing comma is not allowed in match blocks
The arms are tried from the top, and the first whose pattern fits — and whose guard, if it has one, is true — is the one that runs. No other arm runs, and nothing falls through.
else is the default arm. It matches whatever no earlier arm did, and must come last: an arm after it can never be reached, which is error: match arm is unreachable because an earlier pattern matches every value. _ is a pattern, not a default, so _ => as a whole arm is error: use 'else' for the default match arm.
Two arms with the same pattern are refused rather than one silently hiding the other: error: duplicate pattern in match. A runtime arm takes one pattern; several values that share an outcome are written as separate arms, or as a range or a guard.
The subject
The subject is any expression and is evaluated exactly once, before the first arm is tried. Like a condition, it ends at the first { that could open the arms, so a structure literal subject is parenthesised: match (Point { x: 0, y: 0 }) { … }.
A match normally only reads its subject. Written match <- value { … }, it takes the value over, so that its arms can move the parts they bind — see Copy and move.
Arm bodies
| Body | Use |
|---|---|
| an expression | the arm's value, or a call run for its effect |
a block { … } | several statements, in a match statement |
return, fail, break, continue | leave the function or loop from this arm |
match Direction::East {
.North => {
effect = 1;
},
.East => {
effect = 2;
},
else => {}
}
Statement and expression
A match at the start of a statement is a statement: its arms run for their effect, and it produces no value. Anywhere else — after =, after return, as an argument — it is an expression, and every arm produces the match's value:
let label = match status {
200 => "OK",
404 => "Not Found",
else => "Unknown"
};
The arms of a match expression must agree on a type, as the branches of a conditional do:
error: match arm type mismatch: expected 'char8[..]', found 'int'
An arm that leaves — return, fail, break, continue, or a call that never returns, such as Panic — produces no value and does not take part. Where the context fixes the type, such as an annotated binding or a return, every arm takes it: an unsuffixed literal in an arm must fit it, and none, .Success(…) and .Failure(…) arms are completed by it. See Optionals and Errors.
A statement cannot be followed by a postfix operator: match … { … } catch { … } at the start of a statement is an error. To use the value of a match there, bind it, or wrap the match in parentheses.
Exhaustiveness
A match must not meet a value that no arm accepts. How strictly that is checked depends on the subject's type, and on whether the match is a statement or an expression:
| Subject | Covered by | Statement must cover | Expression must cover |
|---|---|---|---|
| an enum | an arm per member | yes | yes |
| a variant | an arm per case | yes | yes |
| an optional, a fallible or a sum | every level: none and present, each channel, each member | yes | yes |
bool | a true arm and a false arm | no | yes |
| a tuple | every combination of its elements | no | yes |
| an integer, a floating-point number or a character | else, or an arm that binds every value | no | yes |
| a structure | an arm whose field patterns are all irrefutable, or else | no | yes |
A missing case is reported by name:
error: match on 'Color' is not exhaustive; missing Color::Blue
error: match on 'bool8' is not exhaustive; missing false
error: match on '(bool8, bool8)' is not exhaustive; missing (true, false)
error: match on 'int32?' is not exhaustive; missing none
error: match on 'int32' is not exhaustive; its arms do not cover every value
Two rules make coverage predictable:
- A guarded arm covers nothing, because its guard might be
false.true => …, false if ready => …still lacks an unguardedfalse. - Ranges never replace
elsefor a number. A value-producing match on an integer ends withelse, even when its ranges happen to reach every value.
else is allowed on an enum or variant too, but it is a promise made on behalf of cases that do not exist yet: add a case later, and every match that names its cases one by one stops compiling at the spot that needs a decision, while one ending in else quietly gives the new case the default answer.
rux 0.4.0 does not yet check a match expression on a character, a floating-point number or a structure for coverage. One with no
else compiles, and a value that no arm accepts gives a meaningless result. End such a match with else.See also
- Patterns — every pattern an arm can use
- Variants — matching cases and their payloads
- Sums — matching members of a sum
- Conditional — a two-way choice of value
- Learn: Match, Match expression, Exhaustive