Enums and match
A plain enum is a set of named values. The backing integer type is optional (the smallest one that fits by default), and a variant can pick its value.
use std::io;
enum level: u8 { DEBUG, // 0 INFO, // 1 WARN = 10, ERROR, // 11}
fn main() -> void { val l = level::WARN; std::println("{} {} {}", l, l as i32, level::ERROR as i32);}// expect: WARN 10 11Enums compare with == and print their variant’s name.
Payloads
Section titled “Payloads”Variants can carry data: one value, a tuple, or a tuple with named elements.
use std::io;
enum event { KEY: u8, CLICK: (x: i32, y: i32), RESIZE: (i32, i32), QUIT,}
fn main() -> void { val events: event[] = { event::KEY('a'), event::CLICK(10, 20), .RESIZE(800, 600), .QUIT }; for (e) in events { std::println(e); }}// expect: KEY(97)// expect: CLICK(10, 20)// expect: RESIZE(800, 600)// expect: QUIT.QUIT is short for event::QUIT wherever the type is already known.
A variant’s payload is also a field named after it, to read, change or borrow where the value is
known to hold that variant. It’s checked: reading it from another variant stops the program (with a
message in a debug build, a trap in a release one).
@discriminant(x) says which variant x holds.
use std::io;
enum shape { CIRCLE: i32, RECT: (w: i32, h: i32),}
fn main() -> void { var s = shape::RECT(2, 3); std::println("{} {}", s.RECT.w * s.RECT.h, @discriminant(s)); s.RECT.h = 4; std::println(s);}// expect: 6 1// expect: RECT(2, 4)Enums can be generic too:
use std::io;
<T: type>enum tree { LEAF: T, EMPTY,}
fn main() -> void { val t: tree<str> = tree<str>::LEAF("x"); std::println(t);}// expect: LEAF(x)match compares a value against patterns, top to bottom. It’s an expression, and it has to cover
every case: list every variant, or end with default.
use std::io;
enum event { KEY: u8, CLICK: (x: i32, y: i32), QUIT,}
fn describe(e: event) -> str { return match (e) { .KEY(k) if k == 'q' => "quit key", // a guard .KEY(k) => "a key", .CLICK(x, y) => "a click", .QUIT => "quit", };}
fn main() -> void { std::println("{} {} {}", describe(event::KEY('q')), describe(event::CLICK(1, 2)), describe(.QUIT));}// expect: quit key a click quitMissing a case is a compile error:
enum light { RED, YELLOW, GREEN }
fn next(l: light) -> light { return match (l) { .RED => light::GREEN, .GREEN => light::YELLOW, };}// error: YELLOWOther patterns
Section titled “Other patterns”Integers, ranges, strings, booleans and tuples match too, and null matches an empty optional; _
matches anything, and a name binds the value (often with a guard).
use std::io;
fn size(n: i32) -> str { return match (n) { 0 => "none", 1..=9 => "a few", x if x < 0 => "negative", default => "lots", };}
fn main() -> void { std::println("{} {} {} {}", size(0), size(4), size(-2), size(99)); val pair = (2, true); match (pair) { (0, _) => std::println("zero"), (n, true) => std::println("{} and true", n), default => std::println("other"), }}// expect: none a few negative lots// expect: 2 and trueAlternatives
Section titled “Alternatives”p1 | p2 => body matches either pattern. Alternatives can bind a name, as long as each one binds it,
and count toward covering every case:
use std::io;
enum shape { CIRCLE: f64, SQUARE: f64, POINT,}
fn kind(c: u8) -> str { return match (c) { '(' | ')' | '[' | ']' => "bracket", '0'..='9' | '_' => "digit or _", default => "other", };}
fn size(s: shape) -> f64 { return match (s) { .CIRCLE(r) | .SQUARE(r) => r, .POINT => 0.0, };}
fn main() -> void { std::println("{} {} {}", kind(']'), kind('5'), size(shape::SQUARE(2.5)));}// expect: bracket digit or _ 2.5The alternatives sit at the top of an arm: (0 | 1, x) inside a tuple isn’t one. Each alternative
is its own arm with the same guard and body, so a guard runs once for each alternative that matches,
and the body is compiled once for each: keep a long body in a function the arm calls.
Slice patterns
Section titled “Slice patterns”[...] matches a slice, an array or a str (its bytes) by its elements: [] is empty, [x] has
one, [first, ..rest] has at least one and binds the others as a slice, [.., last] reads from the
end, and [a, b, ..] needs at least two. Each element is any pattern, so literals, nested tuples and
x& (a reference to the element in place) work too. A match on a slice has to handle every length:
the arms below cover 0, 1, and 2 or more.
use std::io;
fn describe(xs: i32[..]) -> void { match (xs) { [] => std::println("empty"), [x] => std::println("just {}", x), [0, ..rest] => std::println("0, then {} more", rest.len), [first, .., last] => std::println("{} to {}", first, last), }}
fn sum(xs: i32[..]) -> i32 { match (xs) { [] => return 0, [x, ..rest] => return x + sum(rest), }}
fn main() -> void { val xs: i32[] = { 3, 1, 4, 1, 5 }; describe(xs[0..0]); describe(xs[0..1]); describe(xs[..]); std::println("{}", sum(xs[..]));}// expect: empty// expect: just 3// expect: 3 to 5// expect: 14Binding by reference
Section titled “Binding by reference”A binding copies the payload. .V(x&) binds a reference to the payload in place instead, to read
a large value without copying it, or to change it. A match on a reference (r: slot&) matches what
it refers to, so x& there changes the caller’s value.
use std::io;
enum slot { FULL: i32, FREE,}
fn main() -> void { var s = slot::FULL(1); match (s) { .FULL(n&) => { *n += 10; }, .FREE => {}, } std::println(s);}// expect: FULL(11)Error sets and trait unions are matched the same way.