Match

The match expression lets you compare a value against a series of patterns and run different code for each case. It is similar to switch in other languages but more powerful: patterns can destructure structs, bind fields to local variables, and include if guards. Match is an expression — it returns a value, so you can assign the result or use it directly inside a larger expression.

Simple enum matching

The most common use: branch on an enum variant. Every variant must be covered or a _ wildcard must appear — for an ENUM the compiler checks exhaustiveness, because it knows the whole set of variants.

enum Direction { North, South, East, West }

A struct-enum: its variants carry named fields, which a match arm can destructure.

enum Shape {
  Circle { radius: integer },
  Rect { w: integer, h: integer },
}

A variant whose field is itself an enum.

enum Paint { Solid { rgb: integer }, Clear }
enum Tile { Painted { paint: Paint, n: integer }, Bare }
fn direction_name(d: Direction) -> text {
  match d {
    North => "north",
    South => "south",
    East  => "east",
    West  => "west",
  }
}

A guard reading the field its own pattern bound.

fn size_of(s: Shape) -> text {
  match s {
    Circle { radius } if radius > 10 => "big circle",
    Circle { radius } => "small circle",
    Rect { w, h } if w == h => "square",
    Rect { w, h } => "rect",
  }
}
fn main() {
  assert(direction_name(North) == "north", "simple enum match");
  assert(direction_name(West) == "west", "west");

Match as expression

Because match returns a value you can use it in assignments and arguments.

  score = match Direction.East {
    North | South => 10,
    East | West   => 20,
  };
  assert(score == 20, "match expression: {score}");

Struct-enum destructuring

When an enum has variants with fields (a struct-enum) you can bind the fields to variables in the match arm. Circle { radius } — binds the radius field to a local variable Rect { w, h } — binds both w and h In this short form the variable names must match the field names exactly; write Circle { radius: r } to bind the field under another name.

  shape = Rect { w: 3, h: 4 };
  shape_area = match shape {
    Circle { radius } => radius * radius * 3,
    Rect { w, h }     => w * h,
  };
  assert(shape_area == 12, "struct-enum destructure: {shape_area}");

Patterns inside a field

A field can take a pattern instead of a name. A literal matches only that value, and when the field is itself an enum, a variant matches only that variant and binds its own fields in turn. A field the arm does not name is simply not tested. An arm with a pattern inside a field covers only part of its variant, so the match still needs _ (or an arm that binds the fields plainly) to cover the rest.

  tile = Painted { paint: Solid { rgb: 255 }, n: 2 };
  what = match tile {
    Painted { paint: Clear, n } => "clear x{n}",
    Painted { paint: Solid { rgb: 0 }, n } => "black x{n}",
    Painted { paint: Solid { rgb }, n } => "colour {rgb} x{n}",
    _ => "bare",
  };
  assert(what == "colour 255 x2", "a pattern inside a field: {what}");

Guards

Add if condition after a pattern to restrict when the arm matches. A guard can reject, so a guarded arm never counts towards covering an enum: a match whose arms name every variant but guard them all is still refused.

  v = 42;
  label = match v {
    0      => "zero",
    _ if v < 0 => "negative",
    _ if v > 100 => "large",
    _      => "normal",
  };
  assert(label == "normal", "guard: {label}");

A guard can also read what the PATTERN bound, which is where guards earn their keep — the field is in scope inside the condition.

  assert(size_of(Circle { radius: 20 }) == "big circle", "guard on a binding");
  assert(size_of(Circle { radius: 2 }) == "small circle", "guard on a binding, false");
  assert(size_of(Rect { w: 3, h: 3 }) == "square", "guard comparing two bindings");
  assert(size_of(Rect { w: 3, h: 4 }) == "rect", "guard comparing two bindings, false");

Wildcard _

The underscore _ matches anything, and it must be the LAST arm: it matches everything, so an arm written after it could never be selected, and the compiler says so.

Whether you NEED a _ depends on the subject. For an enum the compiler knows every variant, so it requires them all to be covered or a _ to be present, and refuses the match otherwise — naming the variants you left out. For an integer, character or text subject there is no finite set to check, so no _ is required and none is missed: a value that matches no arm makes the match answer null. That is the case to watch, because nothing warns you — see "When nothing matches" below.

  x = 7;
  kind = match x {
    1 => "one",
    2 => "two",
    _ => "other",
  };
  assert(kind == "other", "wildcard: {kind}");

Integer matching

Match works on integers — each arm is compared with ==.

  name = match 3 {
    1 => "one",
    2 => "two",
    3 => "three",
    _ => "many",
  };
  assert(name == "three", "integer match: {name}");

Range patterns

A numeric arm can match a whole inclusive range with low..=high — handy for grading, bucketing, or any "is it in this band" test.

  grade = match 95 {
    90..=100 => "A",
    80..=89  => "B",
    _        => "lower",
  };
  assert(grade == "A", "range pattern: {grade}");

Null patterns

When the subject is nullable, a null arm handles the absent case explicitly. With null-flow this is the idiomatic way to branch on a T? inside a match.

  maybe: integer? = null;
  presence = match maybe {
    null => "absent",
    0    => "zero",
    _    => "present",
  };
  assert(presence == "absent", "null pattern: {presence}");

Text matching

Match also works on text values.

  greeting = "hi";
  reply = match greeting {
    "hello" => "formal",
    "hi"    => "casual",
    _       => "unknown",
  };
  assert(reply == "casual", "text match: {reply}");

Multiple patterns with |

Combine patterns with | to share the same arm body.

  d = Direction.South;
  axis = match d {
    North | South => "vertical",
    East | West   => "horizontal",
  };
  assert(axis == "vertical", "multi-pattern: {axis}");

| joins variant NAMES. To bind fields in several variants with one arm, list the whole patterns separated by , instead: each pattern binds from its own variant, and the arm body runs for whichever one matched. A name every pattern binds must have the same type in each; a name only some patterns bind is nullable in the body, null when a pattern without it matched.

  long_side = match Rect { w: 3, h: 8 } {
    Circle { radius }, Rect { h: radius } => radius,
    _ => 0,
  };
  assert(long_side == 8, "a field bound from either variant: {long_side}");

Tuple patterns

A tuple subject matches element by element. Write _ for an element you do not care about.

  point = (2, "b");
  where = match point {
    (0, _)   => "origin row",
    (2, "a") => "two-a",
    (2, "b") => "two-b",
    _        => "elsewhere",
  };
  assert(where == "two-b", "tuple pattern: {where}");

An element can be an enum variant with its fields bound, so one arm reads both sides at once, and a guard can compare them.

  a: Shape = Rect { w: 2, h: 3 };
  b: Shape = Circle { radius: 5 };
  bigger = match (a, b) {
    (Rect { w, h }, Circle { radius }) if w * h > radius => "rect",
    (Rect { w, h }, Circle { radius }) => "circle",
    _ => "other",
  };
  assert(bigger == "rect", "variants inside a tuple pattern: {bigger}");

When nothing matches

This match names no _, and 7 is none of its arms. On an enum that would not compile; on a scalar there is no finite set to check, so the match answers null and the null travels on. Give the result a fallback with ??, or add a _ arm — the compiler will not remind you.

  unmatched = match 7 {
    1 => "one",
    2 => "two",
  };
  assert(unmatched == null, "a scalar match that selects no arm answers null");
  assert((unmatched ?? "none") == "none", "so give it a fallback");

Match inside an arm

Match is an expression, so it can appear inside other expressions, including another match's arm.

  n = 15;
  result = match n % 2 == 0 {
    true  => "even",
    false => match n % 3 == 0 {
      true  => "odd multiple of 3",
      false => "odd",
    },
  };
  assert(result == "odd multiple of 3", "nested: {result}");
}