diff options
author | Andy Wingo <wingo@pobox.com> | 2019-11-13 22:26:31 +0100 |
---|---|---|
committer | Andy Wingo <wingo@pobox.com> | 2019-11-13 22:33:09 +0100 |
commit | 44ee8c5559ed2f30df464ba1bffdae24994291b3 (patch) | |
tree | a67d0142a23dcdf502ae96c6299004e726a7c8ab /doc/ref/api-control.texi | |
parent | f4ca107f7fe0b6f1ca2c03b558f16077fc89db04 (diff) | |
download | guile-44ee8c5559ed2f30df464ba1bffdae24994291b3.tar.gz |
Update documentation for with-exception-handler et al
* doc/ref/api-control.texi (Prompt Primitives): Reference the newer
exception facilities.
(Exceptions): Rewrite to use the new exception primitives.
(Exception Terminology): Remove superfluous section.
(Exception Objects): New section.
(Raising and Handling Exceptions): New section.
(Throw and Catch): New section, coalescing the previous catch,
with-throw-handler, and throw sections.
(Exceptions and C): New section, for miscellaneous procedures.
(Handling Errors): Mention the transitional period regarding exception
handling.
* doc/ref/api-debug.texi (Catching Exceptions): Rewrite to use newer
exception facilities.
(Capturing Stacks): Remove, as it's not really recommendable any
more.
(Pre-Unwind Debugging): Rewrite to use the new primitives.
(Standard Error Handling): Add note about transitional status.
(Stack Overflow): Reference new exception section.
* doc/ref/api-scheduling.texi (Mutexes and Condition Variables):
Reference new exception section.
* doc/ref/r6rs.texi (rnrs exceptions, rnrs conditions): Update to
mention compatibility with SRFI-34/35 and to relate to core
exceptions.
* doc/ref/srfi-modules.texi (SRFI-34): Document.
Diffstat (limited to 'doc/ref/api-control.texi')
-rw-r--r-- | doc/ref/api-control.texi | 813 |
1 files changed, 476 insertions, 337 deletions
diff --git a/doc/ref/api-control.texi b/doc/ref/api-control.texi index c0d1c1800..f603e32e3 100644 --- a/doc/ref/api-control.texi +++ b/doc/ref/api-control.texi @@ -18,7 +18,7 @@ flow of Scheme affects C code. * Prompts:: Composable, delimited continuations. * Continuations:: Non-composable continuations. * Multiple Values:: Returning and accepting multiple values. -* Exceptions:: Throwing and catching exceptions. +* Exceptions:: Raising and handling exceptions. * Error Reporting:: Procedures for signaling errors. * Dynamic Wind:: Dealing with non-local entrance/exit. * Fluids and Dynamic States:: Dynamic scope building blocks. @@ -514,12 +514,13 @@ Unwind the dynamic and control context to the nearest prompt named @var{tag}, also passing the given values. @end deffn -C programmers may recognize @code{call-with-prompt} and @code{abort-to-prompt} -as a fancy kind of @code{setjmp} and @code{longjmp}, respectively. Prompts are -indeed quite useful as non-local escape mechanisms. Guile's @code{catch} and -@code{throw} are implemented in terms of prompts. Prompts are more convenient -than @code{longjmp}, in that one has the opportunity to pass multiple values to -the jump target. +C programmers may recognize @code{call-with-prompt} and +@code{abort-to-prompt} as a fancy kind of @code{setjmp} and +@code{longjmp}, respectively. Prompts are indeed quite useful as +non-local escape mechanisms. Guile's @code{with-exception-handler} and +@code{raise-exception} are implemented in terms of prompts. Prompts are +more convenient than @code{longjmp}, in that one has the opportunity to +pass multiple values to the jump target. Also unlike @code{longjmp}, the prompt handler is given the full state of the process that was aborted, as the first argument to the prompt's handler. That @@ -586,7 +587,7 @@ important efficiency consideration to keep in mind. One example where this optimization matters is @dfn{escape continuations}. Escape continuations are delimited continuations whose only use is to make a non-local exit---i.e., to escape from the current -continuation. A common use of escape continuations is when throwing an +continuation. A common use of escape continuations is when handling an exception (@pxref{Exceptions}). The constructs below are syntactic sugar atop prompts to simplify the @@ -996,12 +997,20 @@ For example getting results from @code{partition} in SRFI-1 @cindex error handling @cindex exception handling -A common requirement in applications is to want to jump -@dfn{non-locally} from the depths of a computation back to, say, the -application's main processing loop. Usually, the place that is the -target of the jump is somewhere in the calling stack of procedures that -called the procedure that wants to jump back. For example, typical -logic for a key press driven application might look something like this: +What happens when things go wrong? Guile's exception facility exists to +help answer this question, allowing programs to describe the problem and +to handle the situation in a flexible way. + +When a program runs into a problem, such as division by zero, it will +raise an exception. Sometimes exceptions get raised by Guile on a +program's behalf. Sometimes a program will want to raise exceptions of +its own. Raising an exception stops the current computation and instead +invokes the current exception handler, passing it an exception object +describing the unexpected situation. + +Usually an exception handler will unwind the computation back to some +kind of safe point. For example, typical logic for a key press driven +application might look something like this: @example main-loop: @@ -1016,298 +1025,423 @@ find-file: find-specified-file find-specified-file: - check whether file exists; if not, jump back to main-loop + check whether file exists; if not, raise an exception @dots{} @end example -The jump back to @code{main-loop} could be achieved by returning through -the stack one procedure at a time, using the return value of each -procedure to indicate the error condition, but Guile (like most modern -programming languages) provides an additional mechanism called -@dfn{exception handling} that can be used to implement such jumps much -more conveniently. +In this case, @code{main-loop} can install an exception handler that +would cause any exception raised inside @code{dispatch-key} to print a +warning and jump back to the main loop. + +The following subsections go into more detail about exception objects, +raising exceptions, and handling exceptions. It also presents a +historical interface that was used in Guile's first 25 years and which +won't be going away any time soon. @menu -* Exception Terminology:: Different ways to say the same thing. -* Catch:: Setting up to catch exceptions. -* Throw Handlers:: Handling exceptions before unwinding the stack. -* Throw:: Throwing an exception. -* Exception Implementation:: How Guile implements exceptions. +* Exception Objects:: What went wrong? +* Raising and Handling Exceptions:: What to do when something goes wrong. +* Throw and Catch:: An older approach to exceptions. +* Exceptions and C:: Specialized interfaces for C. @end menu -@node Exception Terminology -@subsubsection Exception Terminology +@node Exception Objects +@subsubsection Exception Objects -There are several variations on the terminology for dealing with -non-local jumps. It is useful to be aware of them, and to realize -that they all refer to the same basic mechanism. +When Guile encounters an exceptional situation, it raises an exception, +where the exception is an object that describes the exceptional +situation. Exception objects are structured data, built on the record +facility (@pxref{Records}). -@itemize @bullet -@item -Actually making a non-local jump may be called @dfn{raising an -exception}, @dfn{raising a signal}, @dfn{throwing an exception} or -@dfn{doing a long jump}. When the jump indicates an error condition, -people may talk about @dfn{signalling}, @dfn{raising} or @dfn{throwing} -@dfn{an error}. +@deftp {Exception Type} &exception +The base exception type. All exception objects are composed of +instances of subtypes of @code{&exception}. +@end deftp -@item -Handling the jump at its target may be referred to as @dfn{catching} or -@dfn{handling} the @dfn{exception}, @dfn{signal} or, where an error -condition is involved, @dfn{error}. -@end itemize +@deffn {Scheme Procedure} exception-type? obj +Return true if @var{obj} is an exception type. +@end deffn -Where @dfn{signal} and @dfn{signalling} are used, special care is needed -to avoid the risk of confusion with POSIX signals. +Exception types exist in a hierarchy. New exception types can be +defined using @code{make-exception-type}. -This manual prefers to speak of throwing and catching exceptions, since -this terminology matches the corresponding Guile primitives. +@deffn {Scheme Procedure} make-exception-type id parent field-names +Return a new exception type named @var{id}, inheriting from +@var{parent}, and with the fields whose names are listed in +@var{field-names}. @var{field-names} must be a list of symbols and must +not contain names already used by @var{parent} or one of its supertypes. +@end deffn -The exception mechanism described in this section has connections with -@dfn{delimited continuations} (@pxref{Prompts}). In particular, -throwing an exception is akin to invoking an @dfn{escape continuation} -(@pxref{Prompt Primitives, @code{call/ec}}). +Exception type objects are record type objects, and as such, one can use +@code{record-constructor} on an exception type to get its constructor. +The constructor will take as many arguments as the exception has fields +(including supertypes). @xref{Records}. +However, @code{record-predicate} and @code{record-accessor} aren't +usually what you want to use as exception type predicates and field +accessors. The reason is, instances of exception types can be composed +into @dfn{compound exceptions}. Exception accessors should pick out the +specific component of a compound exception, and then access the field on +that specific component. -@node Catch -@subsubsection Catching Exceptions +@deffn {Scheme Procedure} make-exception exceptions @dots{} +Return an exception object composed of @var{exceptions}. +@end deffn -@code{catch} is used to set up a target for a possible non-local jump. -The arguments of a @code{catch} expression are a @dfn{key}, which -restricts the set of exceptions to which this @code{catch} applies, a -thunk that specifies the code to execute and one or two @dfn{handler} -procedures that say what to do if an exception is thrown while executing -the code. If the execution thunk executes @dfn{normally}, which means -without throwing any exceptions, the handler procedures are not called -at all. +@deffn {Scheme Procedure} exception? obj +Return true if @var{obj} is an exception object. +@end deffn -When an exception is thrown using the @code{throw} function, the first -argument of the @code{throw} is a symbol that indicates the type of the -exception. For example, Guile throws an exception using the symbol -@code{numerical-overflow} to indicate numerical overflow errors such as -division by zero: +@deffn {Scheme Procedure} exception-predicate type +Return a procedure that will return true if its argument is a simple +exception that is an instance of @var{type}, or a compound exception +composed of such an instance. +@end deffn -@lisp -(/ 1 0) -@result{} -ABORT: (numerical-overflow) -@end lisp +@deffn {Scheme Procedure} exception-accessor rtd proc +Return a procedure that will tail-call @var{proc} on an instance of the +exception type @var{rtd}, or on the component of a compound exception +that is an instance of @var{rtd}. +@end deffn -The @var{key} argument in a @code{catch} expression corresponds to this -symbol. @var{key} may be a specific symbol, such as -@code{numerical-overflow}, in which case the @code{catch} applies -specifically to exceptions of that type; or it may be @code{#t}, which -means that the @code{catch} applies to all exceptions, irrespective of -their type. - -The second argument of a @code{catch} expression should be a thunk -(i.e.@: a procedure that accepts no arguments) that specifies the normal -case code. The @code{catch} is active for the execution of this thunk, -including any code called directly or indirectly by the thunk's body. -Evaluation of the @code{catch} expression activates the catch and then -calls this thunk. - -The third argument of a @code{catch} expression is a handler procedure. -If an exception is thrown, this procedure is called with exactly the -arguments specified by the @code{throw}. Therefore, the handler -procedure must be designed to accept a number of arguments that -corresponds to the number of arguments in all @code{throw} expressions -that can be caught by this @code{catch}. - -The fourth, optional argument of a @code{catch} expression is another -handler procedure, called the @dfn{pre-unwind} handler. It differs from -the third argument in that if an exception is thrown, it is called, -@emph{before} the third argument handler, in exactly the dynamic context -of the @code{throw} expression that threw the exception. This means -that it is useful for capturing or displaying the stack at the point of -the @code{throw}, or for examining other aspects of the dynamic context, -such as fluid values, before the context is unwound back to that of the -prevailing @code{catch}. +Compound exceptions are useful to separately express the different +aspects of a situation. For example, compound exceptions allow a +programmer to say that ``this situation is a programming error, and also +here's a useful message to show to the user, and here are some relevant +objects that can give more information about the error''. This error +could be composed of instances of the @code{&programming-error}, +@code{&message}, and @code{&irritants} exception types. + +The subtyping relationship in exceptions is useful to let +different-but-similar situations to be treated the same; for example +there are many varieties of programming errors (for example, +divide-by-zero or type mismatches), but perhaps there are common ways +that the user would like to handle them all, and that common way might +be different than how one might handle an error originating outside the +program (for example, a file-not-found error). + +The standard exception hierarchy in Guile takes its cues from R6RS, +though the names of some of the types are different. @xref{rnrs +exceptions}, for more details. + +To have access to Guile's exception type hierarchy, import the +@code{(ice-9 exceptions)} module: -@deffn {Scheme Procedure} catch key thunk handler [pre-unwind-handler] -@deffnx {C Function} scm_catch_with_pre_unwind_handler (key, thunk, handler, pre_unwind_handler) -@deffnx {C Function} scm_catch (key, thunk, handler) -Invoke @var{thunk} in the dynamic context of @var{handler} for -exceptions matching @var{key}. If thunk throws to the symbol -@var{key}, then @var{handler} is invoked this way: -@lisp -(handler key args ...) -@end lisp +@example +(use-modules (ice-9 exceptions)) +@end example -@var{key} is a symbol or @code{#t}. +The following diagram gives an overview of the standard exception type +hierarchy. -@var{thunk} takes no arguments. If @var{thunk} returns -normally, that is the return value of @code{catch}. +@example +&exception +|- &warning +|- &message +|- &irritants +|- &origin +\- &error + |- &external-error + \- &programming-error + |- &assertion-failure + |- &non-continuable + |- &implementation-restriction + |- &lexical + |- &syntax + \- &undefined-variable +@end example -Handler is invoked outside the scope of its own @code{catch}. -If @var{handler} again throws to the same key, a new handler -from further up the call chain is invoked. +@deftp {Exception Type} &warning +An exception type denoting warnings. These are usually raised using +@code{#:continuable? #t}; see the @code{raise-exception} documentation +for more. +@end deftp +@deffn {Scheme Procedure} make-warning +@deffnx {Scheme Procedure} warning? obj +Constructor and predicate for @code{&warning} exception objects. +@end deffn -If the key is @code{#t}, then a throw to @emph{any} symbol will -match this call to @code{catch}. +@deftp {Exception Type} &message message +An exception type that provides a message to display to the user. +Usually used as a component of a compound exception. +@end deftp +@deffn {Scheme Procedure} make-exception-with-message message +@deffnx {Scheme Procedure} exception-with-message? obj +@deffnx {Scheme Procedure} exception-message exn +Constructor, predicate, and accessor for @code{&message} exception +objects. +@end deffn -If a @var{pre-unwind-handler} is given and @var{thunk} throws -an exception that matches @var{key}, Guile calls the -@var{pre-unwind-handler} before unwinding the dynamic state and -invoking the main @var{handler}. @var{pre-unwind-handler} should -be a procedure with the same signature as @var{handler}, that -is @code{(lambda (key . args))}. It is typically used to save -the stack at the point where the exception occurred, but can also -query other parts of the dynamic state at that point, such as -fluid values. +@deftp {Exception Type} &irritants irritants +An exception type that provides a list of objects that were unexpected +in some way. Usually used as a component of a compound exception. +@end deftp +@deffn {Scheme Procedure} make-exception-with-irritants irritants +@deffnx {Scheme Procedure} exception-with-irritants? obj +@deffnx {Scheme Procedure} exception-irritants exn +Constructor, predicate, and accessor for @code{&irritants} exception +objects. +@end deffn -A @var{pre-unwind-handler} can exit either normally or non-locally. -If it exits normally, Guile unwinds the stack and dynamic context -and then calls the normal (third argument) handler. If it exits -non-locally, that exit determines the continuation. +@deftp {Exception Type} &origin origin +An exception type that indicates the origin of an exception, typically +expressed as a procedure name, as a symbol. Usually used as a component +of a compound exception. +@end deftp +@deffn {Scheme Procedure} make-exception-with-origin origin +@deffnx {Scheme Procedure} exception-with-origin? obj +@deffnx {Scheme Procedure} exception-origin exn +Constructor, predicate, and accessor for @code{&origin} exception +objects. @end deffn -If a handler procedure needs to match a variety of @code{throw} -expressions with varying numbers of arguments, you should write it like -this: +@deftp {Exception Type} &error +An exception type denoting errors: situations that are not just +exceptional, but wrong. +@end deftp +@deffn {Scheme Procedure} make-error +@deffnx {Scheme Procedure} error? obj +Constructor and predicate for @code{&error} exception objects. +@end deffn -@lisp -(lambda (key . args) - @dots{}) -@end lisp +@deftp {Exception Type} &external-error +An exception type denoting errors that proceed from the interaction of +the program with the world, for example a ``file not found'' error. +@end deftp +@deffn {Scheme Procedure} make-external-error +@deffnx {Scheme Procedure} external-error? obj +Constructor and predicate for @code{&external-error} exception objects. +@end deffn -@noindent -The @var{key} argument is guaranteed always to be present, because a -@code{throw} without a @var{key} is not valid. The number and -interpretation of the @var{args} varies from one type of exception to -another, but should be specified by the documentation for each exception -type. - -Note that, once the normal (post-unwind) handler procedure is invoked, -the catch that led to the handler procedure being called is no longer -active. Therefore, if the handler procedure itself throws an exception, -that exception can only be caught by another active catch higher up the -call stack, if there is one. +@deftp {Exception Type} &programming-error +An exception type denoting errors that proceed from inside a program: +type mismatches and so on. +@end deftp +@deffn {Scheme Procedure} make-programming-error +@deffnx {Scheme Procedure} programming-error? obj +Constructor and predicate for @code{&programming-error} exception +objects. +@end deffn -@sp 1 -@deftypefn {C Function} SCM scm_c_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data, scm_t_catch_handler pre_unwind_handler, void *pre_unwind_handler_data) -@deftypefnx {C Function} SCM scm_internal_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data) -The above @code{scm_catch_with_pre_unwind_handler} and @code{scm_catch} -take Scheme procedures as body and handler arguments. -@code{scm_c_catch} and @code{scm_internal_catch} are equivalents taking -C functions. +@deftp {Exception Type} &non-continuable +An exception type denoting errors that proceed from inside a program: +type mismatches and so on. +@end deftp +@deffn {Scheme Procedure} make-non-continuable-error +@deffnx {Scheme Procedure} non-continuable-error? obj +Constructor and predicate for @code{&non-continuable} exception objects. +@end deffn -@var{body} is called as @code{@var{body} (@var{body_data})} with a catch -on exceptions of the given @var{tag} type. If an exception is caught, -@var{pre_unwind_handler} and @var{handler} are called as -@code{@var{handler} (@var{handler_data}, @var{key}, @var{args})}. -@var{key} and @var{args} are the @code{SCM} key and argument list from -the @code{throw}. +@deftp {Exception Type} &lexical +An exception type denoting lexical errors, for example unbalanced +parentheses. +@end deftp +@deffn {Scheme Procedure} make-lexical-error +@deffnx {Scheme Procedure} lexical-error? obj +Constructor and predicate for @code{&lexical} exception objects. +@end deffn -@tpindex scm_t_catch_body -@tpindex scm_t_catch_handler -@var{body} and @var{handler} should have the following prototypes. -@code{scm_t_catch_body} and @code{scm_t_catch_handler} are pointer -typedefs for these. +@deftp {Exception Type} &syntax form subform +An exception type denoting syntax errors, for example a @code{cond} +expression with invalid syntax. The @var{form} field indicates the form +containing the error, and @var{subform} indicates the unexpected +subcomponent, or @code{#f} if unavailable. +@end deftp +@deffn {Scheme Procedure} make-syntax-error form subform +@deffnx {Scheme Procedure} syntax-error? obj +@deffnx {Scheme Procedure} syntax-error-form exn +@deffnx {Scheme Procedure} syntax-error-subform exn +Constructor, predicate, and accessors for @code{&syntax} exception +objects. +@end deffn -@example -SCM body (void *data); -SCM handler (void *data, SCM key, SCM args); -@end example +@deftp {Exception Type} &undefined-variable +An exception type denoting undefined variables. +@end deftp +@deffn {Scheme Procedure} make-undefine-variable-error +@deffnx {Scheme Procedure} undefined-variable-error? obj +Constructor and predicate for @code{&undefined-variable} exception +objects. +@end deffn -The @var{body_data} and @var{handler_data} parameters are passed to -the respective calls so an application can communicate extra -information to those functions. +Incidentally, the @code{(ice-9 exceptions)} module also includes a +@code{define-exception-type} macro that can be used to conveniently add +new exception types to the hierarchy. + +@deffn {Syntax} define-exception-type name parent @ + constructor predicate @ + (field accessor) @dots{} +Define @var{name} to be a new exception type, inheriting from +@var{parent}. Define @var{constructor} and @var{predicate} to be the +exception constructor and predicate, respectively, and define an +@var{accessor} for each @var{field}. +@end deffn -If the data consists of an @code{SCM} object, care should be taken that -it isn't garbage collected while still required. If the @code{SCM} is a -local C variable, one way to protect it is to pass a pointer to that -variable as the data parameter, since the C compiler will then know the -value must be held on the stack. Another way is to use -@code{scm_remember_upto_here_1} (@pxref{Foreign Object Memory -Management}). -@end deftypefn +@node Raising and Handling Exceptions +@subsubsection Raising and Handling Exceptions -@node Throw Handlers -@subsubsection Throw Handlers +An exception object describes an exceptional situation. To bring that +description to the attention of the user or to handle the situation +programmatically, the first step is to @dfn{raise} the exception. -It's sometimes useful 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. The @code{with-throw-handler} -procedure provides a way to do this. +@deffn {Scheme Procedure} raise-exception obj [#:continuable=#f] +Raise an exception by invoking the current exception handler on +@var{obj}. The handler is called with a continuation whose dynamic +environment is that of the call to @code{raise}, except that the current +exception handler is the one that was in place when the handler being +called was installed. -@deffn {Scheme Procedure} with-throw-handler key thunk handler -@deffnx {C Function} scm_with_throw_handler (key, thunk, handler) -Add @var{handler} to the dynamic context as a throw handler -for key @var{key}, then invoke @var{thunk}. +If @var{continuable?} is true, the handler is invoked in tail position +relative to the @code{raise-exception} call. Otherwise if the handler +returns, a non-continuable exception of type @code{&non-continuable} is +raised in the same dynamic environment as the handler. +@end deffn + +As the above description notes, Guile has a notion of a @dfn{current +exception handler}. At the REPL, this exception handler may enter a +recursive debugger; in a standalone program, it may simply print a +representation of the error and exit. -This behaves exactly like @code{catch}, except that it does not unwind -the stack before invoking @var{handler}. If the @var{handler} procedure -returns normally, Guile rethrows the same exception again to the next -innermost catch or throw handler. @var{handler} may exit nonlocally, of -course, via an explicit throw or via invoking a continuation. +To establish an exception handler within the dynamic extent of a call, +use @code{with-exception-handler}. + +@deffn {Scheme Procedure} with-exception-handler handler thunk @ + [#:unwind?=#f] [#:unwind-for-type=#t] +Establish @var{handler}, a procedure of one argument, as the current +exception handler during the dynamic extent of invoking @var{thunk}. + +If @code{raise-exception} is called during the dynamic extent of +invoking @var{thunk}, @var{handler} will be invoked on the argument of +@code{raise-exception}. @end deffn -Typically @var{handler} is used to display a backtrace of the stack at -the point where the corresponding @code{throw} occurred, or to save off -this information for possible display later. +There are two kinds of exception handlers: unwinding and non-unwinding. + +By default, exception handlers are non-unwinding. Unless +@code{with-exception-handler} was invoked with @code{#:unwind? #t}, +exception handlers are invoked within the continuation of the error, +without unwinding the stack. The dynamic environment of the handler +call will be that of the @code{raise-exception} call, with the +difference that the current exception handler will be ``unwound'' to the +\"outer\" handler (the one that was in place when the corresponding +@code{with-exception-handler} was called). + +However, it's often the case that one would like to handle an exception +by unwinding the computation to an earlier state and running the error +handler there. After all, unless the @code{raise-exception} call is +continuable, the exception handler needs to abort the continuation. To +support this use case, if @code{with-exception-handler} was invoked with +@code{#:unwind? #t} is true, @code{raise-exception} will first unwind +the stack by invoking an @dfn{escape continuation} (@pxref{Prompt +Primitives, @code{call/ec}}), and then invoke the handler with the +continuation of the @code{with-exception-handler} call. + +Finally, one more wrinkle: for unwinding exception handlers, it can be +useful to Guile if it can determine whether an exception handler would +indeed handle a particular exception or not. This is especially the +case for exceptions raised in resource-exhaustion scenarios like +@code{stack-overflow} or @code{out-of-memory}, where you want to +immediately shrink resource use before recovering. @xref{Stack +Overflow}. For this purpose, the @code{#:unwind-for-type} keyword +argument allows users to specify the kind of exception handled by an +exception handler; if @code{#t}, all exceptions will be handled; if an +exception type object, only exceptions of that type will be handled; +otherwise if a symbol, only that exceptions with the given +@code{exception-kind} will be handled. + + +@node Throw and Catch +@subsubsection Throw and Catch + +Guile only adopted @code{with-exception-handler} and +@code{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: @code{catch}, +@code{with-throw-handler}, and @code{throw}. -Not unwinding the stack means that throwing an exception that is handled -via a throw handler is equivalent to calling the throw handler handler -inline instead of each @code{throw}, and then omitting the surrounding -@code{with-throw-handler}. In other words, +@deffn {Scheme Procedure} catch key thunk handler [pre-unwind-handler] +@deffnx {C Function} scm_catch_with_pre_unwind_handler (key, thunk, handler, pre_unwind_handler) +@deffnx {C Function} scm_catch (key, thunk, handler) +Establish an exception handler during the dynamic extent of the call to +@var{thunk}. @var{key} is either @code{#t}, indicating that all +exceptions should be handled, or a symbol, restricting the exceptions +handled to those having the @var{key} as their @code{exception-kind}. + +If @var{thunk} executes normally, meaning without throwing any +exceptions, the handler procedures are not called at all and the result +of the @code{thunk} call is the result of the @code{catch}. Otherwise +if an exception is thrown that matches @var{key}, @var{handler} is +called with the continuation of the @code{catch} call. +@end deffn -@lisp -(with-throw-handler 'key - (lambda () @dots{} (throw 'key args @dots{}) @dots{}) - handler) -@end lisp +Given the discussion from the previous section, it is most precise and +concise to specify what @code{catch} does by expressing it in terms of +@code{with-exception-handler}. Calling @code{catch} with the three +arguments is the same as: -@noindent -is mostly equivalent to +@example +(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)) +@end example -@lisp -((lambda () @dots{} (handler 'key args @dots{}) @dots{})) -@end lisp +By invoking @code{with-exception-handler} with @code{#:unwind? #t}, +@code{catch} sets up an escape continuation that will be invoked in an +exceptional situation before the handler is called. -In particular, the dynamic context when @var{handler} is invoked is that -of the site where @code{throw} is called. The examples are not quite -equivalent, because the body of a @code{with-throw-handler} is not in -tail position with respect to the @code{with-throw-handler}, and if -@var{handler} exits normally, Guile arranges to rethrow the error, but -hopefully the intention is clear. (For an introduction to what is meant -by dynamic context, @xref{Dynamic Wind}.) +If @code{catch} is called with four arguments, then the use of +@var{thunk} should be replaced with: -@deftypefn {C Function} SCM scm_c_with_throw_handler (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data, int lazy_catch_p) -The above @code{scm_with_throw_handler} takes Scheme procedures as body -(thunk) and handler arguments. @code{scm_c_with_throw_handler} is an -equivalent taking C functions. See @code{scm_c_catch} (@pxref{Catch}) -for a description of the parameters, the behaviour however of course -follows @code{with-throw-handler}. -@end deftypefn +@example + (lambda () + (with-throw-handler key thunk pre-unwind-handler)) +@end example + +As can be seen above, if a pre-unwind-handler is passed to @code{catch}, +it's like calling @code{with-throw-handler} inside the body thunk. + +@code{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. -If @var{thunk} throws an exception, Guile handles that exception by -invoking the innermost @code{catch} or throw handler whose key matches -that of the exception. When the innermost thing is a throw handler, -Guile calls the specified handler procedure using @code{(apply -@var{handler} key args)}. The handler procedure may either return -normally or exit non-locally. If it returns normally, Guile passes the -exception on to the next innermost @code{catch} or throw handler. If it -exits non-locally, that exit determines the continuation. - -The behaviour of a throw handler is very similar to that of a -@code{catch} expression's optional pre-unwind handler. In particular, a -throw handler's handler procedure is invoked in the exact dynamic -context of the @code{throw} expression, just as a pre-unwind handler is. -@code{with-throw-handler} may be seen as a half-@code{catch}: it does -everything that a @code{catch} would do until the point where -@code{catch} would start unwinding the stack and dynamic context, but -then it rethrows to the next innermost @code{catch} or throw handler -instead. - -Note also that since the dynamic context is not unwound, if a -@code{with-throw-handler} handler throws to a key that does not match -the @code{with-throw-handler} expression's @var{key}, the new throw may -be handled by a @code{catch} or throw handler that is @emph{closer} to -the throw than the first @code{with-throw-handler}. +@deffn {Scheme Procedure} with-throw-handler key thunk handler +@deffnx {C Function} scm_with_throw_handler (key, thunk, handler) +Add @var{handler} to the dynamic context as a throw handler +for key @var{key}, then invoke @var{thunk}. +@end deffn + +It's not possible to exactly express @code{with-throw-handler} in terms +of @code{with-exception-handler}, but we can get close. + +@example +(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)) +@end example +As you can see, unlike in the case of @code{catch}, the handler for +@code{with-throw-handler} is invoked within the continuation of +@code{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 @code{with-throw-handler} that can't be shown +above is that if invoking the handler causes a @code{raise-exception} +instead of completing normally, the exception is thrown in the +@emph{original} dynamic environment of the @code{raise-exception}. Any +inner exception handler will get another shot at handling the exception. Here is an example to illustrate this behavior: @lisp @@ -1328,101 +1462,101 @@ Here is an example to illustrate this behavior: This code will call @code{inner-handler} and then continue with the continuation of the inner @code{catch}. - -@node Throw -@subsubsection Throwing Exceptions - -The @code{throw} primitive is used to throw an exception. One argument, -the @var{key}, is mandatory, and must be a symbol; it indicates the type -of exception that is being thrown. Following the @var{key}, -@code{throw} accepts any number of additional arguments, whose meaning -depends on the exception type. The documentation for each possible type -of exception should specify the additional arguments that are expected -for that kind of exception. +Finally, we get to @code{throw}, which is the older equivalent to +@code{raise-exception}. @deffn {Scheme Procedure} throw key arg @dots{} @deffnx {C Function} scm_throw (key, args) -Invoke the catch form matching @var{key}, passing @var{arg} @dots{} to -the @var{handler}. +Raise an exception with kind @var{key} and arguments @var{args}. +@var{key} is a symbol, denoting the ``kind'' of the exception. +@end deffn -@var{key} is a symbol. It will match catches of the same symbol or of -@code{#t}. +Again, we can specify what @code{throw} does by expressing it in terms +of @code{raise-exception}. -If there is no handler at all, Guile prints an error and then exits. +@example +(define (throw key . args) + (raise-exception (make-exception-from-throw key args))) +@end example + +At this point, we should mention the primitive that manage the +relationship between structured exception objects @code{throw}. + +@deffn {Scheme Procedure} make-exception-from-throw key args +Create an exception object for the given @var{key} and @var{args} passed +to @code{throw}. This may be a specific type of exception, for example +@code{&programming-error}; Guile maintains a set of custom transformers +for the various @var{key} values that have been used historically. @end deffn -When an exception is thrown, it will be caught by the innermost -@code{catch} or throw handler that applies to the type of the thrown -exception; in other words, whose @var{key} is either @code{#t} or the -same symbol as that used in the @code{throw} expression. Once Guile has -identified the appropriate @code{catch} or throw handler, it handles the -exception by applying the relevant handler procedure(s) to the arguments -of the @code{throw}. +@deffn {Scheme Procedure} exception-kind exn +If @var{exn} is an exception created via +@code{make-exception-from-throw}, return the corresponding @var{key} for +the exception. Otherwise, unless @var{exn} is an exception of a type +with a known mapping to @code{throw}, return the symbol +@code{%exception}. +@end deffn -If there is no appropriate @code{catch} or throw handler for a thrown -exception, Guile prints an error to the current error port indicating an -uncaught exception, and then exits. In practice, it is quite difficult -to observe this behaviour, because Guile when used interactively -installs a top level @code{catch} handler that will catch all exceptions -and print an appropriate error message @emph{without} exiting. For -example, this is what happens if you try to throw an unhandled exception -in the standard Guile REPL; note that Guile's command loop continues -after the error message: +@deffn {Scheme Procedure} exception-args exn +If @var{exn} is an exception created via +@code{make-exception-from-throw}, return the corresponding @var{args} +for the exception. Otherwise, unless @var{exn} is an exception of a +type with a known mapping to @code{throw}, return @code{(list @var{exn})}. +@end deffn -@lisp -guile> (throw 'badex) -<unnamed port>:3:1: In procedure gsubr-apply @dots{} -<unnamed port>:3:1: unhandled-exception: badex -ABORT: (misc-error) -guile> -@end lisp -The default uncaught exception behaviour can be observed by evaluating a -@code{throw} expression from the shell command line: +@node Exceptions and C +@subsubsection Exceptions and C + +There are some specific versions of Guile's original @code{catch} and +@code{with-throw-handler} exception-handling primitives that are still +widely used in C code. + +@deftypefn {C Function} SCM scm_c_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data, scm_t_catch_handler pre_unwind_handler, void *pre_unwind_handler_data) +@deftypefnx {C Function} SCM scm_internal_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data) +The above @code{scm_catch_with_pre_unwind_handler} and @code{scm_catch} +take Scheme procedures as body and handler arguments. +@code{scm_c_catch} and @code{scm_internal_catch} are equivalents taking +C functions. + +@var{body} is called as @code{@var{body} (@var{body_data})} with a catch +on exceptions of the given @var{tag} type. If an exception is caught, +@var{pre_unwind_handler} and @var{handler} are called as +@code{@var{handler} (@var{handler_data}, @var{key}, @var{args})}. +@var{key} and @var{args} are the @code{SCM} key and argument list from +the @code{throw}. + +@tpindex scm_t_catch_body +@tpindex scm_t_catch_handler +@var{body} and @var{handler} should have the following prototypes. +@code{scm_t_catch_body} and @code{scm_t_catch_handler} are pointer +typedefs for these. @example -$ guile -c "(begin (throw 'badex) (display \"here\\n\"))" -guile: uncaught throw to badex: () -$ +SCM body (void *data); +SCM handler (void *data, SCM key, SCM args); @end example -@noindent -That Guile exits immediately following the uncaught exception -is shown by the absence of any output from the @code{display} -expression, because Guile never gets to the point of evaluating that -expression. - - -@node Exception Implementation -@subsubsection How Guile Implements Exceptions - -It is traditional in Scheme to implement exception systems using -@code{call-with-current-continuation}. Continuations -(@pxref{Continuations}) are such a powerful concept that any other -control mechanism --- including @code{catch} and @code{throw} --- can be -implemented in terms of them. - -Guile does not implement @code{catch} and @code{throw} like this, -though. Why not? Because Guile is specifically designed to be easy to -integrate with applications written in C. In a mixed Scheme/C -environment, the concept of @dfn{continuation} must logically include -``what happens next'' in the C parts of the application as well as the -Scheme parts, and it turns out that the only reasonable way of -implementing continuations like this is to save and restore the complete -C stack. - -So Guile's implementation of @code{call-with-current-continuation} is a -stack copying one. This allows it to interact well with ordinary C -code, but means that creating and calling a continuation is slowed down -by the time that it takes to copy the C stack. - -The more targeted mechanism provided by @code{catch} and @code{throw} -does not need to save and restore the C stack because the @code{throw} -always jumps to a location higher up the stack of the code that executes -the @code{throw}. Therefore Guile implements the @code{catch} and -@code{throw} primitives independently of -@code{call-with-current-continuation}, in a way that takes advantage of -this @emph{upwards only} nature of exceptions. +The @var{body_data} and @var{handler_data} parameters are passed to +the respective calls so an application can communicate extra +information to those functions. + +If the data consists of an @code{SCM} object, care should be taken that +it isn't garbage collected while still required. If the @code{SCM} is a +local C variable, one way to protect it is to pass a pointer to that +variable as the data parameter, since the C compiler will then know the +value must be held on the stack. Another way is to use +@code{scm_remember_upto_here_1} (@pxref{Foreign Object Memory +Management}). +@end deftypefn + +@deftypefn {C Function} SCM scm_c_with_throw_handler (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data, int lazy_catch_p) +The above @code{scm_with_throw_handler} takes Scheme procedures as body +(thunk) and handler arguments. @code{scm_c_with_throw_handler} is an +equivalent taking C functions. See @code{scm_c_catch} +(@pxref{Exceptions and C}) for a description of the parameters, the +behaviour however of course follows @code{with-throw-handler}. +@end deftypefn @node Error Reporting @@ -2023,8 +2157,13 @@ Guile's parameters conform to SRFI-39 (@pxref{SRFI-39}). @node Handling Errors @subsection How to Handle Errors -Error handling is based on @code{catch} and @code{throw}. Errors are -always thrown with a @var{key} and four arguments: +Guile is currently in a transition from its historical @code{catch} and +@code{throw} error handling and signaling operators to the new +structured exception facility; @xref{Exceptions}. However in the +meantime, here is some documentation on errors and the older +@code{catch} and @code{throw} interface. + +Errors are always thrown with a @var{key} and four arguments: @itemize @bullet @item |