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 ]
PatternFitsBinds
.Stopthe unit case Stopnothing
.Forward(steps)every Forwardsteps, its one value
.Jump(across, _)every Jumpacross; _ ignores the second
.Jump(across, 0)a Jump whose second value is 0across
.Say { text }every Saythe field text, as text
.Say { text: message }every Saythe field text, as message
.Say { loud: true }a Say whose loud is truenothing; other fields are ignored
Command::Stopthe unit case Stopnothing
  • The short form .Case is allowed wherever the subject's type is known to be the variant. Type::Case names 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: pattern binds 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.

Payload tests do not combine.
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