Sum Patterns

The only way to take a value out of a sum is a pattern in a match (or a catch arm, for an error sum). A sum pattern selects members by their type.

typed-pattern = ( identifier | "_" ) ":" type
PatternMatchesBinds
v: Tthe member Tv as a T
_: Tthe member Tnothing
v: A | Bany of the members A, Bv as the smaller sum A | B
Type::Case(p)the variant member Type, holding Casewhatever p binds
elseeverything leftnothing

Guards, else and every other match feature work as usual. See Patterns for the full grammar.

Typed patterns

v: T selects the member T and binds it at the member's type, so the arm can use everything a T has:

func Describe(shape: Circle | Square | Rectangle) -> char8[..] {
    return match shape {
        c: Circle if c.radius == 0.0 => "a point",
        _: Circle => "a circle",
        box: Square | Rectangle => match box {
            _: Square => "a square box",
            _: Rectangle => "a rectangular box"
        }
    };
}

The type must name a member exactly — an int32 member is not matched by n: int64:

error: type 'int64' is not a member or subset of sum 'bool8 | int32'

A bare type name is not a pattern. An identifier in a pattern binds a new name, and a name that already means something else is rejected rather than silently binding the whole subject:

error: pattern 'int32' cannot bind a new variable because 'int32' already names a type
  help: write 'int32Value: int32' to select the member, 'int32 { ... }' to destructure it, or 'Type::Case' to select a case

Subset patterns

v: A | B selects several members at once and binds them as the smaller sum A | B. The binding is still a sum: to reach a member's fields, match it again, as box is matched above. box.side in that arm is an error, because a Rectangle has no side:

error: type 'Rectangle | Square' has no field 'side'

A subset pattern is how a value narrows. A plain assignment from a wider sum to a smaller one is rejected; an arm that binds the subset produces a value of the smaller type that can be passed on.

Variant members

When a member is a variant, a qualified case pattern selects the member and its case in one step:

variant Token {
    Number(int32),
    Name(char8[..])
}

func Kind(value: Token | bool | int32) -> int32 {
    return match value {
        Token::Number(n) => n,
        Token::Name(_) => -1,
        flag: bool => flag ? 1 : 0,
        else => 99
    };
}

The qualification is required. An unqualified .Case pattern works on a variant subject, but on a sum it is ambiguous by construction:

error: case pattern '.Missing' cannot select from sum 'DecodeError | IoError'
  help: write 'DecodeError::Missing' to select the member and its case

A qualified case is ambiguous, too, when two members are instantiations of the same generic variant:

error: case pattern on 'Slot' is ambiguous in 'Slot<bool8> | Slot<int32>': more than one member is that variant
  help: select one instantiation with a typed pattern, as in 'v: Slot<bool8>', and match it separately

Coverage

A match on a sum must cover every member. The diagnostic names the first one missing:

error: match on 'bool8 | char8[..] | int32' is not exhaustive; missing _: char8[..]

An arm whose members are all covered by earlier unguarded arms is unreachable, and so is an error:

func Pick(v: A | B | C) -> int32 {
    return match v {
        ab: A | B => 1,
        a: A => 2,      // error: every A went to the first arm
        _: C => 3
    };
}
error: match arm is unreachable because earlier arms already match every value it matches

An else arm is the one exception: it is never reported as unreachable, even when the arms before it already cover every member. That keeps generic code valid (below).

Borrowed subjects

A match on a borrowed sum inspects it in place, and the subject stays with its owner. Through an exclusive borrow &var, a typed pattern binding one member writes through to the original:

func Grow(shape: &var (Circle | Square)) {
    match shape {
        c: Circle => {
            c.radius = c.radius * 2.0;
        },
        else => {}
    }
}

A subset binding of a borrowed subject can be read and matched, but not borrowed again, stored or moved. A match on an owned sum consumes it: arms that bind a member own it, and an arm that binds nothing destroys it. See Ownership.

Generic sums

A pattern over T | U is checked again at every instantiation. When T and U are the same type, the sum collapses to it, so _: T already covers everything — the closing else covers nothing, which is allowed:

func Left<T, U>(value: T | U) -> bool {
    return match value {
        _: T => true,
        else => false
    };
}

Left<int32, bool>(true) is false; Left<int32, int32>(5) is true.

A concrete type in a generic match.
A typed pattern that names a concrete member, such as n: int32 => on a T | U subject, is meant to select that member after substitution. rux 0.4.0 accepts it but fails while lowering the program, with error: cannot lower the selection of 'int32' from 'T | U'. Name the type parameters in the patterns instead.
Arms that build a sum.
An annotation is meant to give its type to every arm of a match expression, so that let r: int32 | bool = match n { 0 => false, else => n }; injects each arm into the sum. rux 0.4.0 does not do this yet and reports match arm type mismatch: expected 'bool8', found 'int32'. Return the value from a function whose return type is the sum, where each return is injected on its own.

See also