Guile only adopted with-exception-handler
and
raise-exception
as its primary exception-handling facility in
2019. Before then, exception handling was fundamentally based on three
other primitives with a somewhat more complex interface: catch
,
with-throw-handler
, and throw
.
Establish an exception handler during the dynamic extent of the call to
thunk. key is either #t
, indicating that all
exceptions should be handled, or a symbol, restricting the exceptions
handled to those having the key as their exception-kind
.
If thunk executes normally, meaning without throwing any
exceptions, the handler procedures are not called at all and the result
of the thunk
call is the result of the catch
. Otherwise
if an exception is thrown that matches key, handler is
called with the continuation of the catch
call.
Given the discussion from the previous section, it is most precise and
concise to specify what catch
does by expressing it in terms of
with-exception-handler
. Calling catch
with the three
arguments is the same as:
(define (catch key thunk handler) (with-exception-handler (lambda (exn) (apply handler (exception-kind exn) (exception-args exn))) thunk #:unwind? #t #:unwind-for-type key))
By invoking with-exception-handler
with #:unwind? #t
,
catch
sets up an escape continuation that will be invoked in an
exceptional situation before the handler is called.
If catch
is called with four arguments, then the use of
thunk should be replaced with:
(lambda () (with-throw-handler key thunk pre-unwind-handler))
As can be seen above, if a pre-unwind-handler is passed to catch
,
it’s like calling with-throw-handler
inside the body thunk.
with-throw-handler
is the second of the older primitives, and is
used to be able to intercept an exception that is being thrown before
the stack is unwound. This could be to clean up some related state, to
print a backtrace, or to pass information about the exception to a
debugger, for example.
Add handler to the dynamic context as a throw handler for key key, then invoke thunk.
It’s not possible to exactly express with-throw-handler
in terms
of with-exception-handler
, but we can get close.
(define (with-throw-handler key thunk handler) (with-exception-handler (lambda (exn) (when (or (eq? key #t) (eq? key (exception-kind exn))) (apply handler (exception-kind exn) (exception-args exn))) (raise-exception exn)) thunk))
As you can see, unlike in the case of catch
, the handler for
with-throw-handler
is invoked within the continuation of
raise-exception
, before unwinding the stack. If the throw
handler returns normally, the exception will be re-raised, to be handled
by the next exception handler.
The special wrinkle of with-throw-handler
that can’t be shown
above is that if invoking the handler causes a raise-exception
instead of completing normally, the exception is thrown in the
original dynamic environment of the raise-exception
. Any
inner exception handler will get another shot at handling the exception.
Here is an example to illustrate this behavior:
(catch 'a (lambda () (with-throw-handler 'b (lambda () (catch 'a (lambda () (throw 'b)) inner-handler)) (lambda (key . args) (throw 'a)))) outer-handler)
This code will call inner-handler
and then continue with the
continuation of the inner catch
.
Finally, we get to throw
, which is the older equivalent to
raise-exception
.
Raise an exception with kind key and arguments args. key is a symbol, denoting the “kind” of the exception.
Again, we can specify what throw
does by expressing it in terms
of raise-exception
.
(define (throw key . args) (raise-exception (make-exception-from-throw key args)))
At this point, we should mention the primitive that manage the
relationship between structured exception objects throw
.
Create an exception object for the given key and args passed
to throw
. This may be a specific type of exception, for example
&programming-error
; Guile maintains a set of custom transformers
for the various key values that have been used historically.
If exn is an exception created via
make-exception-from-throw
, return the corresponding key for
the exception. Otherwise, unless exn is an exception of a type
with a known mapping to throw
, return the symbol
%exception
.
If exn is an exception created via
make-exception-from-throw
, return the corresponding args
for the exception. Otherwise, unless exn is an exception of a
type with a known mapping to throw
, return (list exn)
.