Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pattern Matching

Pattern matching is one of Kāra's most expressive features. The match expression lets you destructure data and branch on its shape — and the compiler guarantees you handle every case.

Basic matching

fn classify(n: i64) -> String {
    match n {
        0 => "zero",
        1 | 2 | 3 => "small",
        4..=9 => "medium",
        _ => "large",
    }
}
  • | matches multiple values.
  • ..= matches inclusive ranges.
  • _ is the wildcard — matches anything.

Destructuring enums

This is where match really shines:

enum Message {
    Quit,
    Echo(String),
    Move { x: i64, y: i64 },
}

fn handle(msg: Message) {
    match msg {
        Message.Quit => println("Goodbye."),
        Message.Echo(text) => println(f"Echo: {text}"),
        Message.Move { x, y } => println(f"Moving to ({x}, {y})"),
    }
}

Each variant's data is extracted directly into variables. No casting, no type-checking at runtime — the compiler knows the structure at compile time.

Destructuring structs

struct Point {
    x: f64,
    y: f64,
}

fn describe(p: Point) -> String {
    match p {
        Point { x: 0.0, y: 0.0 } => "origin",
        Point { x, y: 0.0 } => f"on the x-axis at {x}",
        Point { x: 0.0, y } => f"on the y-axis at {y}",
        Point { x, y } => f"at ({x}, {y})",
    }
}

Nested patterns

Patterns compose. Match on the shape of nested data:

fn get_name(user: Option[User]) -> String {
    match user {
        Some(User { name, .. }) => name,
        None => "anonymous",
    }
}

.. ignores the remaining fields you don't care about.

Guards

Add conditions with if:

fn classify_temp(temp: f64) -> String {
    match temp {
        t if t < 0.0 => "freezing",
        t if t < 20.0 => "cold",
        t if t < 30.0 => "comfortable",
        _ => "hot",
    }
}

The variable t binds the matched value, and the if clause adds an extra condition.

Exhaustiveness

The compiler requires that match covers every possible case. This is enforced at compile time:

enum Color {
    Red,
    Green,
    Blue,
}

fn name(c: Color) -> String {
    match c {
        Color.Red => "red",
        Color.Green => "green",
        // compile error: non-exhaustive match — Color.Blue not covered
    }
}

This is especially powerful with enums: if you add a new variant, the compiler tells you everywhere you need to handle it. No silent bugs from forgotten cases.

let patterns

You can destructure in let bindings too:

let Point { x, y } = get_point();
let (first, second) = get_pair();

if let

For when you only care about one variant:

if let Some(user) = find_user(42) {
    println(f"Found: {user.name}");
}

This is cleaner than a full match when you'd just ignore the other cases.