Pattern matching in practice
match is Vertex’s main decision-making construct. Once you are
comfortable with the basics (Chapter 6 of the tour), you will start
reaching for it instead of if chains in almost every case. This
guide collects the patterns that are not obvious from the reference.
Matching is the default, if is the exception
Section titled “Matching is the default, if is the exception”A rough heuristic: if the decision involves values, use if; if it
involves shapes, use match.
// Values: a simple inequalityif total > 1000 { apply_discount() } else { full_price() }
// Shapes: a sum-type variantmatch payment { Card(last4:) if fraud_check_failed(last4) => Error(Fraud), Card(..) => Ok(Authorized), Bank(..) => Ok(PendingClearance), Cash => Ok(Authorized),}An if chain that dispatches on the variant of a sum type is almost
always a match waiting to happen.
Guards let you combine values and shapes
Section titled “Guards let you combine values and shapes”Guards are the if inside an arm. They let one arm match “a Card
variant whose last4 looks suspicious” without carving it out as a
separate type.
match transaction { Sale(amount:) if amount > limit => Error(OverLimit), Sale(amount:) => Ok(amount), Refund(amount:) => Ok(-amount),}Order matters: the first arm that matches wins. Put the guarded arm before the unguarded arm for the same variant.
Destructure records at the call site
Section titled “Destructure records at the call site”Single-variant record types destructure cleanly:
type Invoice { Invoice(id: Id, customer: Id, total: Decimal, tax: Decimal)}
fn describe(inv: Invoice): String { let Invoice(customer:, total:, tax:, ..) = inv "customer ${customer} owes ${total} plus ${tax} tax"}The .. silently ignores the unmentioned fields (here, id). If you
want different names, use the long form field: binding:
match inv { Invoice(total: t, tax:, ..) => debug "${t + tax}",}Nested patterns
Section titled “Nested patterns”Patterns nest. Match a Result of a sum type variant in one step:
match charge_card(amount) { Ok(Authorized) => receipt(), Ok(PendingClearance) => "wait for it", Error(CardDecline(Insufficient)) => "try a different card", Error(CardDecline(Expired)) => "card expired", Error(NetworkFailure) => "we'll retry in a bit",}Each layer is a separate pattern. This tends to replace two or three
levels of nested match in other languages.
List patterns
Section titled “List patterns”Lists match by position, with .. collecting the tail.
match events { [] => "nothing happened", [e] => "one event: ${e.name}", [first, ...rest] => "first: ${first.name}, then ${rest.size()} more",}OR patterns for shared arms
Section titled “OR patterns for shared arms”When two variants take the same action, group them with |:
match state { Pending | InReview => debug "waiting", Approved => debug "green light", Rejected => debug "blocked",}OR patterns cannot bind different shapes (you cannot Card(x) | Bank(x))
because the bound names would have different meanings.
When exhaustiveness bites
Section titled “When exhaustiveness bites”The compiler checks match arms for exhaustiveness. If you add a new
variant to a sum type, every match that forgot to handle it fails to
compile. That is the point.
When you genuinely do not care about the other variants, use a wildcard:
match status { Approved => ship(), _ => hold(),}The trap: a careless wildcard swallows the variant you added last
month. Use _ only when “everything else” really is one action.
let assert for invariants
Section titled “let assert for invariants”let assert checks at runtime that a pattern matches and binds the
inner values, crashing if it doesn’t. It is the right tool for
assertions that are not user input:
let assert Ok(config) = load_config() // dev-time invariantlet assert [first, ..] = required_list // provable elsewhereFor real user input, return Result and match properly.
Reading order for the reference
Section titled “Reading order for the reference”If you want the full rulebook:
- Pattern Matching reference: the authoritative list of pattern forms.
- Sum Types reference: how variants are declared and what you can destructure.
- Control Flow reference: how
matchfits alongsideifandfor..in.