Control flow and pattern matching
if
if is an expression, so it produces a value and both branches must have the
same type:
let label = if count == 1 { "item" } else { "items" };
else if chains as you would expect:
fn describe(n: Nat) -> String {
if n == 0 { "empty" } else if n < 10 { "small" } else { "large" }
}
An if used as a statement, with no else, must have type Unit — see
Functions for how that interacts with return.
match
match takes a value apart by constructor:
fn describe(opt: Option<Nat>) -> String {
match opt {
None() => "nothing",
Some(n) => "got a number",
}
}
Arms are separated by commas. Bind a constructor’s payload by naming it, ignore
it with _, or catch everything remaining with a bare _:
match value {
SpecificCase => 1,
_ => 0,
}
Exhaustiveness is checked
Every constructor must be covered, or a wildcard must be present. A missing case is a compile error rather than a runtime failure — which is the main reason to prefer a sum type over a sentinel value.
Nullary constructors
A constructor with no arguments can be written bare in a pattern — Nil => —
as long as some other constructor of the same type carries a payload.
None() with empty parentheses is always accepted and is the form you will see
most in existing code. When no constructor of the type has a payload —
type Color = Red | Green | Blue — the parentheses are required: a bare Red =>
is refused, with the misleading message
match on non-sum types` is not supported.
Matching on numbers
Since 2.0-alpha
A match on a Nat or an Int takes literal patterns, | between literals,
and if guards, and needs one catch-all arm without a guard. See
Signed integers.
Nested patterns
Since 1.5 — v1.0 allowed only one level of destructuring, so this had to be written as nested
matchexpressions.
Patterns nest to arbitrary depth, including tuples inside constructors:
fn unwrap_twice(o: Option<Option<Nat>>) -> Nat {
match o {
Some(Some(n)) => n,
Some(None()) => 1,
None() => 0,
}
}
match parsed {
Ok((token, cursor)) => token,
Err(e) => fallback,
}
if let
Since 1.5
When only one case is interesting, if let avoids a two-arm match:
if let Some(n) = maybe_value {
n + 1
} else {
0
}
Conditions chain with &&, binding several patterns at once. All must match for
the branch to be taken:
fn both(a: Result<Nat, String>, b: Result<Nat, String>) -> Nat {
if let Ok(x) = a && let Ok(y) = b { x + y } else { 0 }
}
let else
Since 1.5
let else binds a refutable pattern in the common case and takes an escape
route otherwise. The else block must diverge — it has to leave the
enclosing function rather than produce a value:
fn or_default(o: Option<Nat>) -> Nat {
let Some(n) = o else { return 0 };
n
}
Note the shape: no semicolon after return 0 inside the block, and one after
the closing brace. n is in scope for the rest of the function, which is the
advantage over match — the happy path does not get indented.