Skip to content

Pattern Matching

Pattern matching is one of Nic's most powerful features. It combines destructuring, conditionals, and exhaustiveness checking.

Basic Matching

nic
fn describe(n: i32) -> string {
    return match n {
        0 -> "zero",
        1 -> "one",
        _ -> "other"
    };
}

The _ wildcard matches anything and is used as a catch-all.

Variable Binding

Bind matched values to names:

nic
enum Option[T] {
    Some(T),
    None
}

fn unwrap_or_default(opt: Option[i32]) -> i32 {
    return match opt {
        Some(x) -> x,      // x is bound to the inner value
        None -> 0
    };
}

Tuple Patterns

Destructure tuples:

nic
fn process_pair(p: (i32, string)) -> unit {
    match p {
        (0, s) -> println("zero with " + s),
        (n, "special") -> println("special case"),
        (n, s) -> println("general case")
    };
    return;
}

Struct Patterns

Match on struct fields:

nic
struct Point {
    x: i32,
    y: i32
}

fn classify_point(p: Point) -> string {
    return match p {
        Point{x: 0, y: 0} -> "origin",
        Point{x: 0, y: _} -> "on y-axis",
        Point{x: _, y: 0} -> "on x-axis",
        Point{x: x, y: y} -> "general"
    };
}

Enum Patterns

Extract data from enum variants:

nic
enum Shape {
    Circle(f64),
    Rectangle(f64, f64)
}

fn area(s: Shape) -> f64 {
    return match s {
        Circle(r) -> 3.14159 * r * r,
        Rectangle(w, h) -> w * h
    };
}

Guards

Add conditions to patterns with if:

nic
fn classify(n: i32) -> string {
    return match n {
        x if x < 0 -> "negative",
        0 -> "zero",
        x if x < 10 -> "small",
        x if x < 100 -> "medium",
        _ -> "large"
    };
}

Guards make patterns more specific without nested matches.

Nested Patterns

Match deeply nested structures:

nic
enum List[T] {
    Nil,
    Cons(T, *List[T])
}

fn second_element(list: *List[i32]) -> Option[i32] {
    return match *list {
        Cons(_, tail) -> match *tail {
            Cons(x, _) -> Some(x),
            Nil -> None
        },
        Nil -> None
    };
}

List Patterns

Match on arrays and slices:

nic
fn process(arr: []i32) -> i32 {
    return match arr {
        [] -> 0,                        // empty
        [x] -> x,                       // single element
        [x, y] -> x + y,                // exactly two
        [x, y, ...rest] -> x + y,       // two or more, bind rest
        _ -> 0
    };
}

The ...rest syntax captures remaining elements as a slice.

Or Patterns

Match multiple patterns with the same arm:

nic
fn is_vowel(c: char) -> bool {
    return match c {
        'a' | 'e' | 'i' | 'o' | 'u' -> true,
        'A' | 'E' | 'I' | 'O' | 'U' -> true,
        _ -> false
    };
}

Exhaustiveness

Match expressions must cover all possibilities. The compiler will error if cases are missing:

nic
enum Color { Red, Green, Blue }

fn name(c: Color) -> string {
    return match c {
        Red -> "red",
        Green -> "green"
        // ERROR: non-exhaustive patterns, Blue not covered
    };
}

Fix by adding the missing case or a wildcard:

nic
fn name(c: Color) -> string {
    return match c {
        Red -> "red",
        Green -> "green",
        Blue -> "blue"      // all cases covered
    };
}

Let Patterns

Destructure in let statements (irrefutable patterns only):

nic
fn main() -> unit {
    let (x, y) = (1, 2);
    let Point{x: px, y: py} = Point{x: 10, y: 20};
    
    return;
}

View Patterns

Transform before matching:

nic
fn parse_int(s: string) -> Option[i32] {
    // parsing logic...
    return None;
}

fn read_number(input: string) -> i32 {
    return match input {
        view parse_int -> Some(n) -> n,
        _ -> 0
    };
}

The view function is applied to the scrutinee, and the result is matched.

Pattern Synonyms

Define reusable pattern aliases:

nic
enum List[T] {
    Nil,
    Cons(T, *List[T])
}

pattern NonEmpty(h, t) = Cons(h, t)
pattern Single(x) = Cons(x, Nil)

fn head[T](list: *List[T]) -> Option[T] {
    return match *list {
        NonEmpty(h, _) -> Some(h),
        Nil -> None
    };
}

Summary

PatternExample
Literal0, "hello", true
Wildcard_
Variablex, name
Tuple(a, b, c)
StructPoint{x: x, y: y}
EnumSome(x), None
Guardx if x > 0
OrA | B | C
List[a, b, ...rest]
Viewview f -> pattern

Next

Learn about Generics for polymorphic code.

Released under the MIT License.