Matching variants
A variant's payload can only be read once it is known which case is held, and match does both in one step. A case pattern fits only values of its case, and when it fits, the names inside it are new bindings holding that case's data. No pattern can both fit a Turn and bind a Forward's data, which is the guarantee a variant gives.
variant Command {
Forward(int),
Turn,
Jump(int, int),
Stop
}
func Cost(command: Command) -> int {
return match command {
.Forward(steps) => steps,
.Turn => 1,
.Jump(across, _) => across * 2,
.Stop => 0
};
}
This page covers what is particular to variants. The match expression and statement, arms, guards and every other kind of pattern are described in match and Patterns.
Case patterns
case-pattern = ( "." | VariantType "::" ) case-name [ payload-pattern ]
payload-pattern = "(" pattern { "," pattern } ")" // positional case
| "{" field-pattern { "," field-pattern } "}" // named case
field-pattern = name [ ":" pattern ]
| Pattern | Fits | Binds |
|---|---|---|
.Stop | the unit case Stop | nothing |
.Forward(steps) | every Forward | steps, its one value |
.Jump(across, _) | every Jump | across; _ ignores the second |
.Jump(across, 0) | a Jump whose second value is 0 | across |
.Say { text } | every Say | the field text, as text |
.Say { text: message } | every Say | the field text, as message |
.Say { loud: true } | a Say whose loud is true | nothing; other fields are ignored |
Command::Stop | the unit case Stop | nothing |
- The short form
.Caseis allowed wherever the subject's type is known to be the variant.Type::Casenames the type as well, and is needed where the subject is a sum containing the variant. - A positional payload lists every value, each as a pattern: a name binds it,
_ignores it, a literal or a nested pattern tests it. The count must match the case:pattern for 'Command::Jump' expects 2 fields, but found 1. - A named payload may leave fields out; an omitted field matches anything. A field written alone binds a variable of its own name, and
field: patternbinds or tests it under another name. The fields may come in any order. - A unit case is written without parentheses,
.Stop.
Patterns nest. A payload pattern can be another case pattern, an enum case, a tuple or a struct pattern:
enum Direction { North, East, South, West }
variant Command {
Move(Direction, int),
Jump(int, int),
Say { text: char8[..]; loud: bool; },
Stop
}
func Describe(command: &Command) -> char8[..] {
return match command {
.Move(.North, 0) => "face north",
.Move(_, steps) if steps > 10 => "a long walk",
.Move(_, _) => "a walk",
.Jump(across, 0) => across > 3 ? "a long flat jump" : "a flat jump",
.Jump(_, _) => "a jump",
.Say { loud: true } => "a shout",
.Say { text } => text,
Command::Stop => "stop"
};
}
Arms are tried from top to bottom, and the first that fits runs, so a specific arm goes above the general one for the same case. An arm may add a guard, if condition, which is checked after the pattern fits and may use its bindings; when the guard is false the next arm is tried.
Bindings
A binding exists only inside its own arm: steps is unknown in the .Turn arm, and using it there is name 'steps' is not defined in this scope.
When the subject is a value the match owns, the bindings take the payload over. When the subject is a reference, as command: &Command above, the case is matched in place and the bindings read the payload where it lies; nothing is copied out of the caller's value.
A bare name in a pattern always binds a new variable. A name that is a case of the subject is refused rather than silently matching everything:
error: pattern 'Stop' cannot bind a new variable because 'Stop' is a case of variant 'Command'
help: write 'Command::Stop' to select the case
Exhaustiveness
A match on a variant — an expression or a statement — must cover every case. A missing case is named:
error: match on 'Command' is not exhaustive; missing Command::Stop
A case counts as covered by an arm that fits every value of it: a unit pattern, or a payload pattern made only of bindings and _, without a guard. An arm with a guard, or one that tests a payload value such as .Move(_, 0), covers only part of the case, so the case still needs a general arm below it.
In rux 0.4.0,
.Light(true) and .Light(false) together do not count as covering Light, although together they match every value — unlike the same two tests on a tuple. End the case with a general arm such as .Light(_).An else arm covers whatever the arms above it leave, and makes a match exhaustive. Prefer naming every case: with else, a case added to the variant later falls silently into the else arm, while without it every incomplete match stops compiling at the place that needs the new arm. The default arm is spelled else; _ => is use 'else' for the default match arm.
Two arms with the same pattern are duplicate pattern in match.
See also
- Variants — declaring and building cases
match— the expression and statement- Patterns — every kind of pattern
- Learn: Variant match, Guard, Exhaustive
Overview
Tagged unions with variant: unit, positional and named cases, generic variants, building a case, structural equality, and the private tag.
Overview
Untagged overlapping storage with union: declaring members, building a union by naming one member, reading another as a reinterpretation, and layout.