Common Lisp the Language, 2nd Edition
Next: Macros
Up: Control Structure
Previous: Rules Governing
the
Common Lisp provides a facility for exiting from a complex process in
a non-local, dynamically scoped manner. There are two classes of special
forms for this purpose, called catch forms and throw
forms, or simply catches and throws. A catch form
evaluates some subforms in such a way that, if a throw form is executed
during such evaluation, the evaluation is aborted at that point and the
catch form immediately returns a value specified by the throw. Unlike
block
and return
(section 7.7), which allow for exiting
a block
form from any point lexically within the body of
the block
, the catch/throw mechanism works even if the
throw form is not textually within the body of the catch form. The throw
need only occur within the extent (time span) of the evaluation of the
body of the catch. This is analogous to the distinction between
dynamically bound (special) variables and lexically bound (local)
variables.
[Special Form]
catch
tag
{
form
}*
The catch
special form serves as a target for transfer
of control by throw
. The form tag is evaluated
first to produce an object that names the catch; it may be any Lisp
object. A catcher is then established with the object as the tag. The
forms are evaluated as an implicit progn
, and the
results of the last form are returned, except that if during the
evaluation of the forms a throw should be executed such that
the tag of the throw matches (is eq
to) the tag of the
catch
and the catcher is the most recent outstanding
catcher with that tag, then the evaluation of the forms is
aborted and the results specified by the throw are immediately returned
from the catch
expression. The catcher established by the
catch
expression is disestablished just before the results
are returned.
The tag is used to match throws with catches.
(catch 'foo
form
)
will
catch a (throw 'foo
form
)
but not a
(throw 'bar
form
)
. It is
an error if throw
is done when there is no suitable
catch
ready to catch it.
Catch tags are compared using eq
, not eql
;
therefore numbers and characters should not be used as catch tags.
Compatibility note: The name catch
comes from MacLisp, but the syntax of catch
in Common Lisp
is different. The MacLisp syntax was
(catch
form
tag
)
,
where the tag was not evaluated.
[Special Form]
unwind-protect
protected-form
{
cleanup-form
}*
Sometimes it is necessary to evaluate a form and make sure that certain side effects take place after the form is evaluated; a typical example is
(progn (start-motor)
(drill-hole)
(stop-motor))
The non-local exit facility of Common Lisp creates a situation in
which the above code won’t work, however: if drill-hole
should do a throw to a catch that is outside of the progn
form (perhaps because the drill bit broke), then
(stop-motor)
will never be evaluated (and the motor will
presumably be left running). This is particularly likely if
drill-hole
causes a Lisp error and the user tells the
error-handler to give up and abort the computation. (A possibly more
practical example might be
(prog2 (open-a-file)
(process-file)
(close-the-file))
where it is desired always to close the file when the computation is
terminated for whatever reason. This case is so important that Common
Lisp provides the special form with-open-file
for this
purpose.)
In order to allow the example hole-drilling program to work, it can
be rewritten using unwind-protect
as follows:
;; Stop the motor no matter what (even if it failed to start).
(unwind-protect
(progn (start-motor)
(drill-hole))
(stop-motor))
If drill-hole
does a throw that attempts to quit out of
the unwind-protect
, then (stop-motor)
will be
executed.
This example assumes that it is correct to call
stop-motor
even if the motor has not yet been started.
Remember that an error or interrupt may cause an exit even before any
initialization forms have been executed. Any state restoration code
should operate correctly no matter where in the protected code an exit
occurred. For example, the following code is not correct:
(unwind-protect
(progn (incf *access-count*)
(perform-access))
(decf *access-count*))
If an exit occurs before completion of the incf
operation the decf
operation will be executed anyway,
resulting in an incorrect value for *access-count*
. The
correct way to code this is as follows:
(let ((old-count *access-count*))
(unwind-protect
(progn (incf *access-count*)
(perform-access))
(setq *access-count* old-count)))
As a general rule, unwind-protect
guarantees to execute
the cleanup-forms before exiting, whether it terminates
normally or is aborted by a throw of some kind. (If, however, an exit
occurs during execution of the cleanup-forms, no special action
is taken. The cleanup-forms of an unwind-protect
are not protected by that unwind-protect
, though they may
be protected if that unwind-protect
occurs within the
protected form of another unwind-protect
.)
unwind-protect
returns whatever results from evaluation of
the protected-form and discards all the results from the
cleanup-forms.
It should be emphasized that unwind-protect
protects
against all attempts to exit from the protected form, including
not only ``dynamic exit’’ facilities such as throw
but also
``lexical exit’’ facilities such as go
and
return-from
. Consider this situation:
(tagbody
(let ((x 3))
(unwind-protect
(if (numberp x) (go out))
(print x)))
out
...)
When the go
is executed, the call to print
is executed first, and then the transfer of control to the tag
out
is completed.
X3J13 voted in March 1989 (EXIT-EXTENT) to clarify the interaction of
unwind-protect
with constructs that perform exits.
Let an exit be a point out of which control can be
transferred. For a throw
the exit is the matching
catch
; for a return-from
the exit is the
corresponding block
. For a go
the exit is the
statement within the tagbody
(the one to which the target
tag belongs) which is being executed at the time the go
is
performed.
The extent of an exit is dynamic; it is not indefinite. The extent of
an exit begins when the corresponding form (catch
,
block
, or tagbody
statement) is entered. When
the extent of an exit has ended, it is no longer legal to return from
it.
Note that the extent of an exit is not the same thing as the scope or
extent of the designator by which the exit is identified. For example, a
block
name has lexical scope but the extent of its exit is
dynamic. The extent of a catch
tag could differ from the
extent of the exit associated with the catch
(which is
exactly what is at issue here). The difference matters when there are
transfers of control from the cleanup clauses of an
unwind-protect
.
When a transfer of control out of an exit is initiated by
throw
, return-from
, or go
, a
variety of events occur before the transfer of control is complete:
unwind-protect
clauses are evaluated.The first edition left the order of these events in some doubt. The
implementation note for throw
hinted that the first two
processes are interwoven, but it was unclear whether it is permissible
for an implementation to abandon all intervening exits before processing
any intervening unwind-protect
cleanup clauses.
The clarification adopted by X3J13 is as follows. Intervening exits
are abandoned as soon as the transfer of control is initiated; in the
case of a throw
, this occurs at the beginning of the
``second pass’’ mentioned in the implementation note. It is an error to
attempt a transfer of control to an exit whose dynamic extent has
ended.
Next the evaluation of unwind-protect
cleanup clauses
and the undoing of dynamic bindings and catch
tags are
performed together, in the order corresponding to the reverse of the
order in which they were established. The effect of this is that the
cleanup clauses of an unwind-protect
will see the same
dynamic bindings of variables and catch
tags as were
visible when the unwind-protect
was entered. (However, some
of those catch
tags may not be useable because they
correspond to abandoned exit points.)
Finally control is transferred to the originally invoked exit and simultaneously that exit is abandoned.
The effect of this specification is that once a program has attempted
to transfer control to a particular exit, an unwind-protect
cleanup form cannot step in and decide to transfer control to a more
recent (nested) exit, blithely forgetting the original exit request.
However, a cleanup form may restate the request to transfer to the same
exit that started the cleanup process.
Here is an example based on a nautical metaphor. The function
gently
moves an oar in the water with low force, but if an
oar gets stuck, the caller will catch a crab. The function
row
takes a boat, an oar-stroking function, a stream, and a
count; an oar is constructed for the boat and stream and the
oar-stroking function is called :count
times. The function
life
rows a particular boat. Merriment follows, except that
if the oarsman is winded he must stop to catch his breath.
(defun gently (oar)
(stroke oar :force 0.5)
(when (stuck oar)
(throw 'crab nil)))
(defun row (boat stroke-fn stream &key count)
(let ((oar (make-oar boat stream)))
(loop repeat count do (funcall stroke-fn oar))))
(defun life ()
(catch 'crab
(catch 'breath
(unwind-protect
(row *your-boat* #'gently *query-io* :count 3))
(when (winded) (throw 'breath nil)))
(loop repeat 4 (set-mode :merry))
(dream))))
Suppose that the oar gets stuck, causing gently
to call
throw
with the tag crab
. The program is then
committed to exiting from the outer catch
(the one with the
tag crab
). As control breaks out of the
unwind-protect
form, the winded
test is
executed. Suppose it is true; then another call to throw
occurs, this time with the tag breath
. The inner
catch
(the one with the tag breath
) has been
abandoned as a result of the first throw
operation (still
in progress). The clarification voted by X3J13 specifies that the
program is in error for attempting to transfer control to an abandoned
exit point. To put it in terms of the example: once you have begun to
catch a crab, you cannot rely on being able to catch your breath.
Implementations may support longer extents for exits than is required by this specification, but portable programs may not rely on such extended extents.
(This specification is somewhat controversial. An alternative
proposal was that the abandoning of exits should be lumped in with the
evaluation of unwind-protect
cleanup clauses and the
undoing of dynamic bindings and catch
tags, performing all
in reverse order of establishment. X3J13 agreed that this approach is
theoretically cleaner and more elegant but also more stringent and of
little additional practical use. There was some concern that a more
stringent specification might be a great added burden to some
implementors and would achieve only a small gain for users.)
[Special Form]
throw
tag
result
The throw
special form transfers control to a matching
catch
construct. The tag is evaluated first to
produce an object called the throw tag; then the result form is
evaluated, and its results are saved (if the result form
produces multiple values, then all the values are saved). The
most recent outstanding catch whose tag matches the throw tag is exited;
the saved results are returned as the value(s) of the catch. A
catch
matches only if the catch tag is eq
to
the throw tag.
In the process, dynamic variable bindings are undone back to the
point of the catch, and any intervening unwind-protect
cleanup code is executed. The result form is evaluated before
the unwinding process commences, and whatever results it produces are
returned from the catch.
If there is no outstanding catcher whose tag matches the throw tag, no unwinding of the stack is performed, and an error is signalled. When the error is signalled, the outstanding catchers and the dynamic variable bindings are those in force at the point of the throw.
Implementation note: These requirements imply that
throwing should typically make two passes over the control stack. In the
first pass it simply searches for a matching catch. In this search every
catch
must be considered, but every
unwind-protect
should be ignored. On the second pass the
stack is actually unwound, one frame at a time, undoing dynamic bindings
and outstanding unwind-protect
constructs in reverse order
of creation until the matching catch is reached.
Compatibility note: The name throw
comes from MacLisp, but the syntax of throw
in Common Lisp
is different. The MacLisp syntax was
(throw
form
tag
)
,
where the tag was not evaluated.
Next: Macros
Up: Control Structure
Previous: Rules Governing
the
AI.Repository@cs.cmu.edu