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

BodyUse
an expressionthe arm's value, or a call run for its effect
a block { … }several statements, in a match statement
return, fail, break, continueleave 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:

SubjectCovered byStatement must coverExpression must cover
an enuman arm per memberyesyes
a variantan arm per caseyesyes
an optional, a fallible or a sumevery level: none and present, each channel, each memberyesyes
boola true arm and a false armnoyes
a tupleevery combination of its elementsnoyes
an integer, a floating-point number or a characterelse, or an arm that binds every valuenoyes
a structurean arm whose field patterns are all irrefutable, or elsenoyes

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 unguarded false.
  • Ranges never replace else for a number. A value-producing match on an integer ends with else, 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.

Characters, floating-point numbers and structures.
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