Common Lisp the Language, 2nd Edition
One important feature that restart-case (or restart-bind) offers that
catch does not is the ability to reason about the available points to
which control might be transferred without actually attempting the
transfer. One could, for example, write
(ignore-errors (throw ...))
which is a sort of poor man's variation of
(when (find-restart 'something) (invoke-restart 'something))
but there is no way to use ignore-errors and throw to simulate something like
(when (and (find-restart 'something) (find-restart 'something-else)) (invoke-restart 'something))
or even just
(when (and (find-restart 'something) (yes-or-no-p "Do something? ")) (invoke-restart 'something))
because the degree of inspectability that comes with simply writing
(ignore-errors (throw ...))
is too primitive-getting the desired information also forces transfer of control, perhaps at a time when it is not desirable.
Many programmers have previously evolved strategies like the following on a case-by-case basis:
(defvar *foo-tag-is-available* nil) (defun fn-1 () (catch 'foo (let ((*foo-tag-is-available* t)) ... (fn-2) ...))) (defun fn-2 () ... (if *foo-tag-is-available* (throw 'foo t)) ...)
The facility provided by restart-case and find-restart is intended to provide a standardized protocol for this sort of information to be communicated between programs that were developed independently so that individual variations from program to program do not thwart the overall modularity and debuggability of programs.
Another difference between the restart facility and the catch/throw
facility is that a catch with any given tag completely shadows any
outer pending catch that uses the same tag. Because of the presence
of compute-restarts, however, it is possible to see shadowed restarts,
which may be very useful in some situations (particularly in an
interactive debugger).