Pattern Matching
Weir has built-in pattern matching with compile-time exhaustiveness checking. Patterns can match on sum type variants, destructure structs, and bind variables.
match Expressions
Section titled “match Expressions”Each case is wrapped in its own parenthesized pair — pattern first, then body:
(match x ((Some val) (use val)) (None (default-value)))match is an expression — it returns a value. All branches must have the same type.
Sum Type Matching
Section titled “Sum Type Matching”Match against variants of a deftype:
(deftype EnemyState Idle (Patrol Vec2 Vec2) (Chase i64) Dead)
(match enemy-state ((Patrol start end) (move-between start end)) ((Chase target-id) (pursue target-id)) (Idle (stand-still)) (Dead (remove-entity)))Nested Patterns
Section titled “Nested Patterns”Patterns can be nested to match deeper structures:
(match opt ((Some (Some x)) (str "doubly wrapped: " x)) ((Some None) "outer Some, inner None") (None "nothing"))Literal Patterns
Section titled “Literal Patterns”Match against specific values:
(match key-code (27 (quit)) ;; ESC (32 (jump)) ;; space (_ (ignore))) ;; anything elseExhaustiveness
Section titled “Exhaustiveness”Pattern matching is exhaustive by default. If you don’t handle all variants, the compiler produces an error:
;; Compile error — missing Dead and Idle(match enemy-state ((Patrol start end) (move-between start end)) ((Chase target-id) (pursue target-id)))Use _ as a wildcard to explicitly handle remaining cases:
(match enemy-state (Dead (remove-entity)) (_ (update-ai entity)))This is a guard rail: when you add a new variant to a sum type, the compiler tells you everywhere you need to handle it. During live reloading, exhaustiveness is re-checked on type redefinition.
Struct Destructuring
Section titled “Struct Destructuring”Structs are destructured using keyword syntax in let bindings and function parameters:
(defstruct Vec2 (x : f64) (y : f64))
;; Destructure all fields (binding names match field names)(let (({:x :y} my-vec)) (+ x y))
;; Partial destructuring (only some fields)(let (({:health} enemy)) (> health 0))Renamed Bindings
Section titled “Renamed Bindings”Put a binding name after the keyword to rename:
;; Rename x→ax, y→ay for the first vec; x→bx, y→by for the second(defn distance (({:x ax :y ay} : Vec2) ({:x bx :y by} : Vec2)) : f64 (sqrt (+ (* (- bx ax) (- bx ax)) (* (- by ay) (- by ay)))))In match Arms
Section titled “In match Arms”Struct destructuring also works in match patterns:
(defn get-x ((v : Vec2)) : f64 (match v ({:x} x)))Irrefutable vs. Refutable Patterns
Section titled “Irrefutable vs. Refutable Patterns”Irrefutable patterns always match. They are allowed in let bindings and function parameters:
- Struct destructuring (a struct always has all its fields)
- Tuple destructuring
- Simple variable binding
Refutable patterns might not match. They are only allowed in match:
- Sum type variants (
Some,None, etc.) - Literal values
;; OK — struct destructuring always succeeds (irrefutable)(let (({:x :y} my-vec)) (+ x y))
;; COMPILE ERROR — Option might be None (refutable)(let (((Some val) maybe-result)) (use val))
;; Correct — use match for refutable patterns(match maybe-result ((Some val) (use val)) (None (default-value)))This is a guard rail: the compiler prevents destructuring that could fail at runtime. If a pattern can fail, you’re forced to handle all cases.
Wildcard _
Section titled “Wildcard _”The _ pattern matches anything and binds nothing:
(match result ((Ok val) (use val)) ((Err _) (println "something went wrong")))Use _ when you need to handle a case but don’t need the contained value.