Next: , Previous: Throw, Up: Exceptions


26.7.4 Catch Without Unwinding

A lazy catch is used in the same way as a normal catch, with key, thunk and handler arguments specifying the exception type, normal case code and handler procedure, but differs in one important respect: the handler procedure is executed without unwinding the call stack from the context of the throw expression that caused the handler to be invoked.

— Scheme Procedure: lazy-catch tag thunk handler
— C Function: scm_lazy_catch (tag, thunk, handler)

Behave like catch, except that the stack is not unwound before invoking handler. The handler procedure is not allowed to return; it must throw to another catch, or otherwise exit non-locally.

Typically, handler should save any desired state associated with the stack at the point where the corresponding throw occurred, and then throw an exception itself — usually the same exception as the one it caught. If handler is invoked and does not throw an exception, Guile itself throws an exception with key misc-error.

Not unwinding the stack means that throwing an exception that is caught by a lazy-catch is almost equivalent to calling the lazy-catch's handler inline instead of each throw, and then omitting the surrounding lazy-catch. In other words,

     (lazy-catch 'key
       (lambda () ... (throw 'key args ...) ...)
       handler)

is almost equivalent to

     ((lambda () ... (handler 'key args ...) ...))

But why only almost? The difference is that with lazy-catch (as with normal catch), the dynamic context is unwound back to just outside the lazy-catch expression before invoking the handler. (For an introduction to what is meant by dynamic context, See Dynamic Wind.)

Then, when the handler itself throws an exception, that exception must be caught by some kind of catch (including perhaps another lazy-catch) higher up the call stack.

The dynamic context also includes with-fluids blocks (REFFIXME), so the effect of unwinding the dynamic context can also be seen in fluid variable values. This is illustrated by the following code, in which the normal case thunk uses with-fluids to temporarily change the value of a fluid:

     (define f (make-fluid))
     (fluid-set! f "top level value")
     
     (define (handler . args)
       (cons (fluid-ref f) args))
     
     (lazy-catch 'foo
                 (lambda ()
                   (with-fluids ((f "local value"))
                     (throw 'foo)))
                 handler)
     ⇒ ("top level value" foo)
     
     ((lambda ()
        (with-fluids ((f "local value"))
          (handler 'foo))))
     ⇒ ("local value" foo)

In the lazy-catch version, the unwinding of dynamic context restores f to its value outside the with-fluids block before the handler is invoked, so the handler's (fluid-ref f) returns the external value.

lazy-catch is useful because it permits the implementation of debuggers and other reflective programming tools that need to access the state of the call stack at the exact point where an exception or an error is thrown. For an example of this, see stack-catch below, available after evaluating the form:

     (use-modules (ice-9 stack-catch))
— Scheme Procedure: stack-catch key thunk handler

Like catch, invoke thunk in the dynamic context of handler for exceptions matching key, but also save the current stack state in the the-last-stack fluid, for the purpose of debugging or re-throwing of an error. If thunk throws to the symbol key, then handler is invoked this way:

          (handler key args ...)

key is a symbol or #t.

thunk takes no arguments. If thunk returns normally, that is the return value of catch.

Handler is invoked outside the scope of its own catch. If handler again throws to the same key, a new handler from further up the call chain is invoked.

If the key is #t, then a throw to any symbol will match this call to catch."