elisp: Catch and Throw
10.6.1 Explicit Nonlocal Exits: ‘catch’ and ‘throw’
---------------------------------------------------
Most control constructs affect only the flow of control within the
construct itself. The function ‘throw’ is the exception to this rule of
normal program execution: it performs a nonlocal exit on request.
(There are other exceptions, but they are for error handling only.)
‘throw’ is used inside a ‘catch’, and jumps back to that ‘catch’. For
example:
(defun foo-outer ()
(catch 'foo
(foo-inner)))
(defun foo-inner ()
...
(if x
(throw 'foo t))
...)
The ‘throw’ form, if executed, transfers control straight back to the
corresponding ‘catch’, which returns immediately. The code following
the ‘throw’ is not executed. The second argument of ‘throw’ is used as
the return value of the ‘catch’.
The function ‘throw’ finds the matching ‘catch’ based on the first
argument: it searches for a ‘catch’ whose first argument is ‘eq’ to the
one specified in the ‘throw’. If there is more than one applicable
‘catch’, the innermost one takes precedence. Thus, in the above
example, the ‘throw’ specifies ‘foo’, and the ‘catch’ in ‘foo-outer’
specifies the same symbol, so that ‘catch’ is the applicable one
(assuming there is no other matching ‘catch’ in between).
Executing ‘throw’ exits all Lisp constructs up to the matching
‘catch’, including function calls. When binding constructs such as
‘let’ or function calls are exited in this way, the bindings are
unbound, just as they are when these constructs exit normally (
Local Variables). Likewise, ‘throw’ restores the buffer and position
saved by ‘save-excursion’ (Excursions), and the narrowing status
saved by ‘save-restriction’. It also runs any cleanups established with
the ‘unwind-protect’ special form when it exits that form (
Cleanups).
The ‘throw’ need not appear lexically within the ‘catch’ that it
jumps to. It can equally well be called from another function called
within the ‘catch’. As long as the ‘throw’ takes place chronologically
after entry to the ‘catch’, and chronologically before exit from it, it
has access to that ‘catch’. This is why ‘throw’ can be used in commands
such as ‘exit-recursive-edit’ that throw back to the editor command loop
(Recursive Editing).
Common Lisp note: Most other versions of Lisp, including Common
Lisp, have several ways of transferring control nonsequentially:
‘return’, ‘return-from’, and ‘go’, for example. Emacs Lisp has
only ‘throw’. The ‘cl-lib’ library provides versions of some of
these. (cl)Blocks and Exits.
-- Special Form: catch tag body...
‘catch’ establishes a return point for the ‘throw’ function. The
return point is distinguished from other such return points by TAG,
which may be any Lisp object except ‘nil’. The argument TAG is
evaluated normally before the return point is established.
With the return point in effect, ‘catch’ evaluates the forms of the
BODY in textual order. If the forms execute normally (without
error or nonlocal exit) the value of the last body form is returned
from the ‘catch’.
If a ‘throw’ is executed during the execution of BODY, specifying
the same value TAG, the ‘catch’ form exits immediately; the value
it returns is whatever was specified as the second argument of
‘throw’.
-- Function: throw tag value
The purpose of ‘throw’ is to return from a return point previously
established with ‘catch’. The argument TAG is used to choose among
the various existing return points; it must be ‘eq’ to the value
specified in the ‘catch’. If multiple return points match TAG, the
innermost one is used.
The argument VALUE is used as the value to return from that
‘catch’.
If no return point is in effect with tag TAG, then a ‘no-catch’
error is signaled with data ‘(TAG VALUE)’.