Typed pattern
A sum knows which member it holds, and a typed pattern asks it. The arm n: int32 => matches only when the active member is int32, and binds that value to n as an int32 — so inside the arm it is an ordinary number again. It prints, it adds, it compares.
In the last lesson a sum could be built, stored and compared, but not printed or used in arithmetic. This lesson is the way back out.
One arm per member
A variant match arm names a case: .Exact(value) =>. A sum has no case names, so its arm names a type instead — the binding, a colon, the member type:
type Setting = int32 | bool | char8[..];
func Show(key: char8[..], value: Setting) {
match value {
seconds: int32 => PrintLine("{:8} = {} ms", key, seconds * 1000),
flag: bool => PrintLine("{:8} = {}", key, flag ? "on" : "off"),
word: char8[..] => PrintLine("{:8} = \"{}\"", key, word)
}
}
Each binding has its member's own type. seconds is an int32, so seconds * 1000 is integer arithmetic. flag is a bool, so it can drive the ternary. word is a text slice, ready for a placeholder.
flowchart LR
v["value: Setting"] --> q{"Which member<br/>is active?"}
q -- "int32" --> a1["seconds: int32 =><br/>seconds * 1000"]
q -- "bool" --> a2["flag: bool =><br/>on or off"]
q -- "char8[..]" --> a3["word: char8[..] =><br/>print the word"]Exactly one arm runs, the one whose type is the active member. The order of the arms does not change which one that is — a member can only ever match its own arm.
A match that produces a value
As an expression, a match on a sum produces one value from whichever arm ran, just as a match expression on a number does:
func Weight(value: Setting) -> int32 {
return match value {
n: int32 => n,
flag: bool => flag ? 1 : 0,
_: char8[..] => -1
};
}
All three arms produce an int32, so the whole match is an int32, whichever member came in.
Matching without a binding
The text arm in Weight needs no value from its member — only the fact that the setting is text. _: char8[..] matches the type without naming it. Use it whenever an arm would otherwise bind a name it never reads.
| Pattern | Matches when the active member is | Binds |
|---|---|---|
n: int32 | int32 | n, an int32 |
_: char8[..] | char8[..] | nothing |
else | anything earlier arms left | nothing |
Every member must be covered
A match on a sum must handle every member, just as a variant match handles every case. Leave out an arm and the compiler names the pattern you still need. Without the int32 arm in Show:
error: match on 'bool8 | char8[..] | int32' is not exhaustive; missing _: int32
The compiler spells the sum in its own sorted order, and bool by its full name bool8, so the members may not appear in the order you wrote them.
When several members are handled alike, an else arm covers whatever the earlier arms left:
match value {
n: int32 => PrintLine("number {}", n),
else => PrintLine("something else")
}
That works, but it gives something up: add a fourth member to Setting later and this match quietly sends it to else. With one arm per member, the compiler stops you and points at every match that needs the new arm. The next lesson shows a middle way — one arm for a chosen group of members.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A sum knows which member it holds, and a typed pattern asks it. The arm `n: int32 =>` matches
// only when the active member is `int32`, and binds that value to `n` *as an int32*, so inside
// the arm it is an ordinary number again: it prints, it adds, it compares.
//
// A variant arm names a case, `.Exact(value) =>`. A sum has no case names, so its arm names a
// type instead: the binding, a colon, the member type. `_: char8[..] =>` matches the member
// without binding it, for when only the fact matters.
//
// The match must cover every member, just as a variant match covers every case. Leave out the
// `int32` arm below and the compiler names the pattern you still need:
// error: match on 'bool8 | char8[..] | int32' is not exhaustive; missing _: int32
// (The compiler spells the sum in its own sorted order, and `bool` by its full name `bool8`.)
import Io::PrintLine;
type Setting = int32 | bool | char8[..];
// One arm per member. Each binding has the member's own type, so `seconds * 1000` is integer
// arithmetic and `word` is a text slice ready for a placeholder.
func Show(key: char8[..], value: Setting) {
match value {
seconds: int32 => PrintLine("{:8} = {} ms", key, seconds * 1000),
flag: bool => PrintLine("{:8} = {}", key, flag ? "on" : "off"),
word: char8[..] => PrintLine("{:8} = \"{}\"", key, word)
}
}
// As an expression, a match on a sum produces one value from whichever arm ran. Here the text
// arm needs no value from its member, so `_` matches the type without naming it.
func Weight(value: Setting) -> int32 {
return match value {
n: int32 => n,
flag: bool => flag ? 1 : 0,
_: char8[..] => -1
};
}
func Main() -> int {
let timeout: Setting = 30;
let verbose: Setting = true;
let mode: Setting = "fast";
Show("timeout", timeout);
Show("verbose", verbose);
Show("mode", mode);
PrintLine("weights {} {} {}", Weight(timeout), Weight(verbose), Weight(mode));
return 0;
}
Run it
cd Examples/SumTypes/TypedPattern
rux run
timeout = 30000 ms
verbose = on
mode = "fast"
weights 30 1 -1
Common mistakes
A match on a sum must be exhaustive. Drop the
int32 arm from Show and it fails with error: match on 'bool8 | char8[..] | int32' is not exhaustive; missing _: int32.n: int64 => on a Setting fails with error: type 'int64' is not a member or subset of sum 'bool8 | char8[..] | int32'. The pattern must name the member exactly — an int32 member is not matched by a wider integer type.An arm
int32 => "number" fails with error: pattern 'int32' cannot bind a new variable because 'int32' already names a type. A bare name in a pattern is a new binding, and a type name cannot be one. Write _: int32 =>, or n: int32 => if you need the value.Try it yourself
- Add
float64toSettingand run the program. Read the error, then add afloat64arm to bothShowandWeight. - Write
func Kind(value: Setting) -> char8[..]that returns"number","switch"or"text". Which patterns need no binding at all? - Rewrite
Showwith oneint32arm and anelsearm. Then add a fourth member toSettingand compare: which version ofShowmakes the compiler complain?
Learn more
- Subset pattern — one arm for several members
- Variant match — the same idea for named cases
matchin the Rux Reference