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
| Pattern | Matches | Binds |
|---|---|---|
v: T | the member T | v as a T |
_: T | the member T | nothing |
v: A | B | any of the members A, B | v as the smaller sum A | B |
Type::Case(p) | the variant member Type, holding Case | whatever p binds |
else | everything left | nothing |
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 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.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
- Sum types — the type and how values enter it
- Type tests —
is, when only the member matters - Match and Patterns
- Learn: Typed pattern, Subset pattern, Generic sum
Overview
The sum type A | B: one value of a set of distinct types, normalized as a set, built by injection and widening, and collapsed after generic substitution.
Type Tests
The is operator: which member of a sum is active, whether an optional is present, or a static check of a value's exact type. It never narrows.