#+TITLE: Conditions and restarts — cheatsheet #+STARTUP: showeverything Why it is shaped this way: [[file:spec-conditions.md][spec-conditions.md]]. Something to poke at: =conditions-play.flan= — =flan dev conditions-play.flan=, then ~C-c C-c~. * Works #+begin_src lisp (signal c) ; Unit. Handler returns -> carry on. No handler -> no-op. (error c) ; Never. Only a transfer gets past; else the program stops. (handler-bind [(Type [c] body ...) ...] body ...) ; match by type, no hierarchy (restart-case BODY ; BODY and every clause have the same type = the form's (name [] CLAUSE) ...) (invoke-restart 'name) ; Never. Innermost frame offering the name wins. #+end_src #+begin_src lisp (defn fetch [n i32] i32 (restart-case (middle n) ; its value if nothing transfers (use-placeholder [] -1) (retry [] 7))) (handler-bind [(AssetMissing [c] (invoke-restart 'use-placeholder))] (fetch 2)) ; -1 #+end_src ~defer~ between the invoke and the target runs, innermost first, before the clause body. ~errdefer~ does not. * The break loop An unhandled ~error~ in a dev build stops on the frame that erred, with nothing unwound, and waits. ~C-c C-b~ in Emacs lists what is on offer and resumes into the choice; ~flan:stopped(Missing)~ in the modeline says it happened. The list is *numbered*, and the number is what is chosen. Two frames offering ~retry~ both appear and §4's by-name walk can only ever reach the first, so a name cannot say which one is meant — ~restart-at~ can. A restart below the evaluation a break is inside is listed, marked, and refused: ~C-x C-e~ runs its thunk through a C frame that holds its own transfer channel, so an unwind aimed past it would stop at the thunk. Choose one offered above it, or ~abort~. * Not yet ~handler-case~ · ~find-restart~ · ~compute-restarts~ · restarts with parameters. Each refused by name with its reason. ~find-restart~ and ~compute-restarts~ are blocked on a type rather than on effort: §4 gives them ~(Option Restart)~ and a list, and there is no ~Restart~ type and no list to return one in. The break loop reads the same stack through the agent's socket instead. * Gotchas - *A handler closes over nothing.* It is lifted into its own function. Accumulate into a global, or put the value on the condition. - *A restart re-runs whatever sits between it and the target.* Control resumes at the ~restart-case~, so a ~retry~ repeats side effects after it. Put the ~restart-case~ where re-entry is safe. - *An unknown restart name is a hard stop.* No ~find-restart~ to test with. - *No supertype*, so nothing can say "any condition". - *~signal~ cannot hand a value back.* Deliberate (§1). - A condition must be a *struct*. ~return~ is refused inside either form. * Stops the program, exit 134 #+begin_example unhandled AssetMissing file.flan:3:25: no restart named nope is active file.flan:4:7: a defer invoked a restart, which a defer may not do — ... #+end_example