← Back to Blog

Pattern Matching Deep Dive

June 10, 2025 languagepatterns

Lateralus pattern matching is exhaustive and expressive. Match on structure, guards, bindings, and more. The compiler verifies you've handled all cases.

◉ Basic Match

let result = match value {
    0 => "zero",
    1 => "one",
    _ => "many",
}

// Match is an expression - returns a value
let description = match status_code {
    200 => "OK",
    404 => "Not Found",
    500 => "Server Error",
    _ => "Unknown",
}

◉ Literal Patterns

// Numbers
match n {
    0 => "zero",
    1 | 2 | 3 => "small",
    4..=10 => "medium",
    _ => "large",
}

// Strings
match command {
    "quit" | "exit" | "q" => exit(0),
    "help" | "?" => show_help(),
    "" => continue,
    cmd => execute(cmd),
}

// Booleans
match active {
    true => "Running",
    false => "Stopped",
}

◉ Destructuring Structs

struct Point { x: float, y: float }

match point {
    // Match specific field values
    Point { x: 0.0, y } => println("on y-axis at " + str(y)),
    Point { x, y: 0.0 } => println("on x-axis at " + str(x)),

    // Bind all fields
    Point { x, y } => println("at (" + str(x) + ", " + str(y) + ")"),
}

// Shorthand when variable name matches field
fn process(p: Point) {
    let Point { x, y } = p  // Destructure in let
    println(x + y)
}

◉ Destructuring Enums

enum Result[T, E] {
    Ok(T),
    Err(E),
}

match result {
    Ok(value) => println("Success: " + str(value)),
    Err(e) => println("Error: " + e.message()),
}

// Nested destructuring
enum Message {
    Quit,
    Move { x: int, y: int },
    Write(str),
    Color(int, int, int),
}

match msg {
    Quit => exit(0),
    Move { x, y } => move_cursor(x, y),
    Write(text) => println(text),
    Color(r, g, b) => set_color(r, g, b),
}

◉ Guards

Add conditions with if:

match n {
    x if x < 0 => "negative",
    0 => "zero",
    x if x > 100 => "large positive",
    x if x % 2 == 0 => "even",
    _ => "odd",
}

// Multiple conditions
match point {
    Point { x, y } if x == y => "on diagonal",
    Point { x, y } if x > 0 && y > 0 => "quadrant I",
    Point { x, y } if x < 0 && y > 0 => "quadrant II",
    Point { x, y } if x < 0 && y < 0 => "quadrant III",
    Point { x, y } if x > 0 && y < 0 => "quadrant IV",
    _ => "on axis",
}

◉ Destructuring Lists

match items {
    [] => "empty",
    [only] => "single: " + str(only),
    [first, second] => "pair",
    [first, ..rest] => "first: " + str(first) + ", rest: " + str(len(rest)),
}

// Head/tail pattern
fn sum(list: [int]) -> int {
    match list {
        [] => 0,
        [head, ..tail] => head + sum(tail),
    }
}

// Specific positions
match coords {
    [x, y, z] => point_3d(x, y, z),
    [x, y] => point_2d(x, y),
    _ => panic("invalid coords"),
}

◉ Binding with @

Bind a value while also matching structure:

match msg {
    // Bind whole variant while extracting fields
    m @ Move { x, y } if x > 100 => {
        println("Large move: " + str(x) + "," + str(y))
        log(m)
    },
    _ => {}
}

// Bind range match
match age {
    n @ 0..=17 => println("Minor, age " + str(n)),
    n @ 18..=64 => println("Adult, age " + str(n)),
    n @ 65.. => println("Senior, age " + str(n)),
}

◉ Option Matching

match find_user(id) {
    Some(user) => greet(user),
    None => println("User not found"),
}

// Nested option
match get_config("timeout") {
    Some(Some(value)) => set_timeout(value),
    Some(None) => set_timeout(default),
    None => println("Config key missing"),
}

◉ Result Matching

match read_file(path) {
    Ok(content) => process(content),
    Err(IOError::NotFound) => create_file(path),
    Err(IOError::Permission) => request_access(path),
    Err(e) => panic("Unexpected: " + e.message()),
}

◉ Exhaustiveness

The compiler ensures all cases are handled:

enum Direction { North, South, East, West }

// ERROR: non-exhaustive pattern
match dir {
    North => go_up(),
    South => go_down(),
    // Missing East and West!
}

// OK: all cases covered
match dir {
    North => go_up(),
    South => go_down(),
    East => go_right(),
    West => go_left(),
}

// OK: wildcard covers remaining
match dir {
    North | South => vertical(),
    _ => horizontal(),
}

◉ If Let

For single-pattern matching:

// Instead of:
match maybe_value {
    Some(v) => process(v),
    None => {},
}

// Use if let:
if let Some(v) = maybe_value {
    process(v)
}

// With else:
if let Ok(data) = parse(input) {
    use(data)
} else {
    println("Parse failed")
}

◉ While Let

// Process until None
while let Some(item) = queue.pop() {
    process(item)
}

// Process until error
while let Ok(line) = reader.read_line() {
    println(line)
}

◉ Match in Pipelines

// Filter by pattern
items
    |> filter(fn(x) {
        match x { Some(_) => true, None => false }
    })

// Transform by pattern
results
    |> map(fn(r) {
        match r {
            Ok(v) => v * 2,
            Err(_) => 0,
        }
    })

◉ Advanced: Nested Patterns

struct User { name: str, address: Option[Address] }
struct Address { city: str, country: str }

match user {
    User { name, address: Some(Address { city: "NYC", .. }) } =>
        println(name + " is in New York"),
    User { name, address: Some(addr) } =>
        println(name + " is in " + addr.city),
    User { name, address: None } =>
        println(name + " has no address"),
}

Pattern matching is central to Lateralus. Combined with exhaustiveness checking, it makes impossible states unrepresentable.