Pattern Matching
Pattern matching is one of Nic's most powerful features. It combines destructuring, conditionals, and exhaustiveness checking.
Basic Matching
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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):
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:
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:
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
| Pattern | Example |
|---|---|
| Literal | 0, "hello", true |
| Wildcard | _ |
| Variable | x, name |
| Tuple | (a, b, c) |
| Struct | Point{x: x, y: y} |
| Enum | Some(x), None |
| Guard | x if x > 0 |
| Or | A | B | C |
| List | [a, b, ...rest] |
| View | view f -> pattern |
Next
Learn about Generics for polymorphic code.