;;;; A condition type names its parent, and a handler for a type answers every ;;;; condition below it. Error is the root every built-in error descends from, ;;;; so one handler for it catches a bad index, an arithmetic failure and a ;;;; program's own error alike — and is handed the name and the runtime's ;;;; sentence, not the fields, since its type is the parent's. ;; A category: a parent with no field vector, which gets Error's two fields. (defstruct IoError :parent Error) (defstruct DiskFull :parent IoError [free i64]) ;; A condition with no parent is matched by its own type and nothing else. (defstruct Loner [n i32]) (defonce zero i64) (defonce grid [4 i32]) (defn risky [n i32] i32 (cond (= n 0) (i32 (/ 10 zero)) (= n 1) (at grid (+ n 5)) (= n 2) (do (error (DiskFull {.free 7})) 0) :else n)) ;; The catch-all. The clause runs after the unwind, so the name and the ;; sentence it prints are copies that outlived the frame that signalled. (defn guarded [n i32] i32 (handler-case (risky n) [(Error [e] (println (.name e)) (println (.message e)) -1)])) (defn main [] i32 (println (guarded 0)) (println (guarded 1)) (println (guarded 2)) (println (guarded 3)) ;; A handler for the middle of the chain. (println (handler-case (risky 2) [(IoError [e] (println (.name e)) -2)])) ;; A handler for the condition's own type still reads its fields, and is ;; the innermost, so it answers first. (println (handler-case (handler-case (risky 2) [(DiskFull [d] (i32 (.free d)))]) [(Error [_e] -3)])) ;; A non-unwinding handler for Error sees the same two fields while the ;; signalling frame is alive, and the handler for ArithError inside it ;; reads the op as the enum it is. (println (handler-case (handler-bind [(Error [e] (println (.message e)))] (handler-bind [(ArithError [a] (println (= (.op a) :div-zero)))] (risky 0))) [(ArithError [_a] -4)])) ;; A condition outside the chain is not an Error. (println (handler-case (handler-case (do (error (Loner {.n 1})) 0) [(Error [_e] -5)]) [(Loner [l] (.n l))])) 0)