Error Handling
Weir treats errors as values using the Result type. Errors are visible in type signatures, and the ? operator provides ergonomic propagation. There are no exceptions.
The Result Type
Section titled “The Result Type”Result is a sum type defined in the prelude:
(deftype (Result 'ok 'err) (Ok 'ok) (Err 'err))Functions that can fail declare this in their return type:
(defn safe-div ((x : i32) (y : i32)) : (Result i32 String) (if (= y 0) (Err "division by zero") (Ok (/ x y))))Handling Results
Section titled “Handling Results”Use pattern matching to handle both cases:
(match (safe-div 10 3) ((Ok val) (println (str "Result: " val))) ((Err msg) (println (str "Error: " msg))))Or define helper functions:
(defn unwrap-or ((r : (Result i64 String)) (default : i64)) : i64 (match r ((Ok val) val) ((Err _) default)))The ? Operator
Section titled “The ? Operator”The ? operator propagates errors up the call stack. Applied to a (Result 'a 'err) expression:
- If
(Ok val)— unwraps toval, execution continues - If
(Err e)— immediately returns(Err e)from the enclosing function
(defn load-level ((path : String)) : (Result Level IOError) (let ((data (read-file path)?) ;; propagates on error (entities (parse-entities data)?)) ;; propagates on error (Ok (Level entities))))Without ?, you’d need nested match expressions:
;; Without ? — verbose(defn load-level ((path : String)) : (Result Level IOError) (match (read-file path) ((Err e) (Err e)) ((Ok data) (match (parse-entities data) ((Err e) (Err e)) ((Ok entities) (Ok (Level entities)))))))The ? operator can only be used inside functions that return Result.
Error Conversion with From
Section titled “Error Conversion with From”When a function can produce multiple error types, the From typeclass enables automatic conversion at ? sites:
;; Define conversions(instance (From IOError GameError) (defn from (e) (GameError :io e)))
(instance (From ParseError GameError) (defn from (e) (GameError :parse e)))
;; ? automatically converts via From(defn load-level ((path : String)) : (Result Level GameError) (let ((data (read-file path)?) ;; IOError → GameError via From (entities (parse-entities data)?)) ;; ParseError → GameError via From (Ok (Level entities))))This pattern mirrors Rust’s From trait and ? operator.
Design Philosophy
Section titled “Design Philosophy”Errors as values align with Weir’s guard rails philosophy:
- Errors are visible in types — you can’t accidentally ignore a fallible operation
- No hidden control flow — unlike exceptions,
?is explicit at every propagation point - Exhaustive handling — pattern matching on
Resultforces you to handle both cases - Composable —
Resultis a regular type that works with all existing language features
;; A list of results(defn try-all ((inputs : (Vector String))) : (Vector (Result i64 String)) (map parse-int inputs))
;; The compiler ensures you handle the error case(match (safe-div x y) ((Ok val) (use val)) ;; forgetting (Err _) here is a compile error )