Common Lisp the Language, 2nd Edition
Blind
transfer of control to a handler-case is only one possible kind
of recovery action that can be taken when a condition is signaled. The
low-level mechanism offers great flexibility in how to continue once
a condition has been signaled.
The basic idea behind condition handling is that a piece of code called the signaler recognizes and announces the existence of an exceptional situation using signal or some function built on signal (such as error).
The process of signaling involves the search for and invocation of a handler, a piece of code that will attempt to deal appropriately with the situation.
If a handler is found, it may either handle the situation, by performing some non-local transfer of control, or decline to handle it, by failing to perform a non-local transfer of control. If it declines, other handlers are sought.
Since the lexical environment of the signaler might not be available to handlers, a data structure called a condition is created to represent explicitly the relevant state of the situation. A condition either is created explicitly using make-condition and then passed to a function such as signal, or is created implicitly by a function such as signal when given appropriate non-condition arguments.
In order to handle the error, a handler is permitted to use any non-local transfer of control such as go to a tag in a tagbody, return from a block, or throw to a catch. In addition, structured abstractions of these primitives are provided for convenience in exception handling.
A handler can be made dynamically accessible to a program by use of handler-bind. For example, to create a handler for a condition of type arithmetic-error, one might write:
(handler-bind ((arithmetic-error handler))body)
The handler is a function of one argument, the condition. If a condition of the designated type is signaled while the body is executing (and there are no intervening handlers), the handler would be invoked on the given condition, allowing it the option of transferring control. For example, one might write a macro that executes a body, returning either its value(s) or the two values nil and the condition:
(defmacro without-arithmetic-errors (&body forms) (let ((tag (gensym))) `(block ,tag (handler-bind ((arithmetic-error #'(lambda (c) ;Argument c is a condition (return-from ,tag (values nil c))))) ,@body))))
The handler is executed in the dynamic context of the signaler, except
that the set of available condition handlers will have been rebound to
the value that was active at the time the condition handler was made
active. If a handler declines (that is, it does not transfer control), other
handlers are sought. If no handler is found and the condition was signaled
by error or cerror (or some function such as assert that behaves like
these functions), the debugger is entered, still in the dynamic context
of the signaler.