Common Lisp the Language, 2nd Edition

next up previous contents index
Next: Macros Up: Control Structure Previous: Rules Governing the

7.11. Dynamic Non-Local Exits

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) 

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) 

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). 

  (progn (start-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:

  (progn (incf *access-count*) 
  (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*)) 
    (progn (incf *access-count*) 
    (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:

  (let ((x 3)) 
      (if (numberp x) (go out)) 
      (print x))) 

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:

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 
        (row *your-boat* #'gently *query-io* :count 3)) 
        (when (winded) (throw 'breath nil))) 
      (loop repeat 4 (set-mode :merry)) 

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 up previous contents index
Next: Macros Up: Control Structure Previous: Rules Governing the