For Gauche 0.9.5

Next: , Previous: , Up: Core library   [Contents][Index]

6.20 Exceptions

Gauche’s exception system consists of three components; (1) the way to signal an exceptional case has occurred, (2) the way to specify how to handle such a case, and (3) the standard objects (conditions) to communicate the code that signals an exceptional case and the code that handles it.

Those three components are typically used together, so first we explain the typical usage patterns using examples. Then we describe each feature in detail.

Note for terminology: some languages use the word exception to refer to an object used to communicate the code that encountered an exceptional situation with a handler that deals with it. Gauche uses a term condition to refer to such objects, following SRFI-35. Exception is the situation, and condition is a runtime object that describes it.

Next: , Previous: , Up: Exceptions   [Contents][Index]

6.20.1 Exception handling overview

Catching specific errors

One of the most typical exception handling is to catch a specific error raised by some built-in or library procedures. A macro guard can be used for such a purpose. The code looks like this:

(guard (exc [(condition-has-type? exc <read-error>)
             (format #t "read error!")
            [else 'other-error])
  (read-from-string "(abc"))

The cadr of guard clause is a form of (variable clause …). In this example, the variable is exc, and it has two clauses. Each clause has the form like the one in cond.

The cddr of guard is the body, a list of expressions. This example has only one expression, (read-from-string "(abc").

guard starts executing its body. read-from-string raises an error of type <read-error> when it encounters syntactic errors. The form guard intercepts the error, and binds the condition object to the variable exc, then checks the clauses following exc in a similar manner to cond—in this case, the thrown condition is of type <read-error>, so the test of the first clause is satisfied, and the rest of clause is executed, i.e. "read error!" is printed and a symbol read-error is returned.

If you’re familiar with other languages, you may recognize the pattern. The cddr of guard form is like try clause of C++/Java or the cadr of handler-case of Common Lisp; and the cdadr of guard form is like catch clauses or the cddr of handler-case.

In the test expressions it is common to check the type of thrown condition. The function condition-has-type? is defined in SRFI-35 but it’s rather lengthy. Gauche’s condition classes can also work like a predicate, so you can write the above expression like this.

(guard (exc [(<read-error> exc)
             (format #t "read error!")
            [else 'other-error])
  (read-from-string "(abc")))

Note: Generally you can’t use is-a? to test if the thrown condition is of a specific type, since a condition may be compound. See Conditions about compound conditions.

If no tests of clauses satisfy and no else clause is given, the exception ‘falls off’ the guard construct, i.e. it will be handled by the outer level of guard form or top-level. For example, the following guard form only handles <read-error> and <system-error>; if the body throws other type of conditions, it must be handled by outer level.

(guard (exc [(<read-error> exc) (handle-read-error)]
            [(<system-error> exc) (handle-system-error)])
  body …)

See Handling exceptions for more details on guard and other lower-level exception handling constructs.

Signaling exceptions from your code

The generic way to signal an exception is to use raise procedure.

(raise condition)

You can pass any object to condition; its interpretation solely depends on the exception handler. If you know the code raises an integer as a condition, you can catch it by guard as this:

(guard (exc [(integer? exc) 'raised])
  (raise 3))

However, as a convention, it is preferable to use an instance of <condition> or one of its subclasses. A macro condition can be used to create a condition object. The following examples show how to create a condition with some slot values and then raise it.

;; create and raise an error condition
(raise (condition
        (<error> (message "An error occurred."))))

;; create and raise a system error condition
(raise (condition
        (<system-error> (message "A system error occurred.")
                        (errno EINTR))))

See Conditions for the details of condition macro and what kind of condition classes are provided.

The most common type of condition is an error condition, so a convenience procedure error and errorf are provided. They create an error condition with a message and raise it.

;; `error' concatenates the arguments into a message.
(unless (integer? obj)
  (error "Integer expected, but got:" obj))

;; `errorf' uses format to create a message.
(unless (equal? x y)
  (errorf "~s and ~s don't match" x y))

Unlike the exception throwing constructs in some languages, such as throw of C++/Java, which abandons its continuation, Scheme’s raise may return to its caller. If you don’t want raise to return, a rule of thumb is always to pass one of error conditions to it; then Gauche guarantees raise wont return. See the description of raise in Signaling exceptions for more details.

Note: R7RS adopted slightly different semantics; it splits raise and raise-continuable, the former is for noncontinuable exception (if the exception handler returns, it raises another error), and the latter is for continuable exception. When you’re in R7RS environment, R7RS-compatible raise will be used instead of this raise.

Defining your own condition

You can also define your own condition classes to pass application-specific information from the point of raising exception to the handlers.

To fit to Gauche’s framework (SRFI-35), it is desirable that the new condition class inherits a built-in <condition> class or one of its descendants, and also is an instance of a metaclass <condition-meta>.

One way of ensuring the above convention as well as increasing portability is to use define-condition-type macro, defined in SRFI-35.

(define-condition-type <myapp-error> <error>
  (debug-info myapp-error-debug-info)
  (reason myapp-error-reason))

This defines a condition type (which is a class in Gauche) <myapp-error>, with a predicate myapp-error? and slots with accessors. Then you can use the new condition type like the following code:

(guard (exc
         [(myapp-error? exc)
          (let ([debug-info (myapp-error-debug-info exc)]
                [reason (myapp-error-reason exc)])
            ... handle myapp-error ...)])
  (if (something-went-wrong)
    (raise (condition
             (<myapp-error> (debug-info "during processing xxx")
                            (reason "something went wrong")))))

If you don’t mind to lose srfi compatibility, you can use Gauche’s extended error and errorf procedures to write more concise code to raise a condition of subtype of <error>:

  (if (something-went-wrong)
    (error <myapp-error>
           :debug-info "during processing xxx"
           :reason "something went wrong"))

See the description of define-condition-type macro for how the condition type is implemented in Gauche’s object system.

Next: , Previous: , Up: Exceptions   [Contents][Index]

6.20.2 Signaling exceptions

Signaling errors

The most common case of exceptions is an error. Two convenience functions to signal an error condition in simple cases are provided. To signal a compound condition, you can use raise as explained below.

Function: error string arg …
Function: error condition-type keyword-arg … string arg …

[R7RS+][SRFI-23+] Signals an error. The first form creates an <error> condition, with a message consists of string and arg …, and raises it. It is compatible to R7RS and SRFI-23’s error behavior.

gosh> (define (check-integer x)
        (unless (integer? x)
           (error "Integer required, but got:" x)))
gosh> (check-integer "a")
*** ERROR: Integer required, but got: "a"
Stack Trace:

The second form can be used to raise an error other than the <error> condition. condition-type must be a condition type (see Conditions for more explanation of condition types). It may be followed by keyword-value list to initialize the condition slots, and then optionally followed by a string and other objects that becomes an error message.

(define-condition-type <my-error> <error> #f

  (unless (memq operation *supported-operations*)
    (error <my-error>
           :reason 'not-supported :priority 'urgent
           "Operation not supported:" operation))
Function: errorf fmt-string arg …
Function: errorf condition-type keyword-arg … fmt-string arg …

Similar to error, but the error message is formatted by format, i.e. the first form is equivalent to:

(define (errorf fmt . args)
  (error (apply format #f fmt args)))

The second form can be used to raise an error other than an <error> condition. Meaning of condition-type and keyword-args are the same as error.

Signaling generic conditions

Function: raise condition

[SRFI-18][R7RS] This is the base mechanism of signaling exceptions.

The procedure invokes the current exception handler. The argument condition represents the nature of the exception, and passed to the exception handler. Gauche’s built-in and library functions always use an instance of <condition> or one of its subclasses as condition, but you can pass any Scheme object to raise. The interpretation of condition is up to the exception handler.

Note: Unlike some of the mainstream languages in which "throwing" an exception never returns, you can set up an exception handler in the way that raise may return. The details are explained in Handling exceptions.

If you don’t want raise to return, the best way is to pass a condition which is an instance of <serious-condition> or one of its subclasses. Gauche’s internal mechanism guarantees raising such an exception won’t return. See Conditions for the hierarchy of built-in conditions.

R7RS adopted slightly different semantics regarding returning from raise; in R7RS, raise never returns—if the exception handler returns, another exception is raised. R7RS has raise-continuable to explicitly allow returning from the exception handler. For portable programs, always pass <serious-condition> or its subclasses to raise.

Next: , Previous: , Up: Exceptions   [Contents][Index]

6.20.3 Handling exceptions

High-level exception handling mechanism

Macro: guard (var clause …) body …

[R7RS][SRFI-34] This is the high-level form to handle errors in Gauche.

var is a symbol, and clauses are the same form as cond’s clauses, i.e. each clause can be either one of the following forms:

  1. (test expr …)
  2. (test => proc)

The last clause may be (else expr …).

This form evaluates body … and returns the value(s) of the last body expression in normal case. If an exception is raised during the evaluation of body expressions, the raised exception is bound to a variable var, then evaluates test expression of each clause. If one of test expressions returns true value, then the corresponding exprs are evaluated if the clause is the first form above, or an proc is evaluated and the result of test is passed to the procedure proc if the clause is the second form.

When the test(s) and expr(s) in the clauses are evaluated, the exception handler that is in effect of the caller of guard are installed; that is, if an exception is raised again within clauses, it is handled by the outer exception handler or guard form.

If no test returns true value and the last clause is else clause, then the associated exprs are evaluated. If no test returns true value and there’s no else clause, the raised exception is re-raised, to be handled by the outer exception handler.

When the exception is handled by one of clauses, guard returns the value(s) of the last expr in the handling clause.

The clauses are evaluated in the same dynamic environment as the guard form, i.e. any dynamic-winds inside body are unwound before evaluation of the clauses. It is different from the lower level forms with-error-handler and with-exception-handler, whose handler is evaluated before the dynamic environment are unwound.

(let ([z '()])
  (guard (e [else (push! z 'caught)])
    (dynamic-wind (lambda () (push! z 'pre))
                  (lambda () (error "foo"))
                  (lambda () (push! z 'post))))
  (reverse z))
 ⇒ (pre post caught)

(guard (e [else (print 'OUTER) #f])
    (lambda ()
      (print 'INNER)
      (error "foo"))))
 ⇒ prints OUTER to the current output port of guard,
      not to the string port.
Macro: unwind-protect expr cleanup …

Executes expr, then executes cleanups, and returns the result(s) of expr. If an exception is raised within expr, cleanups are executed before the exception escapes from the unwind-protect form. For example, the following code calls start-motor, drill-a-hole, and stop-motor in order if everything goes ok, and if anything goes wrong in start-motor or drill-a-hole, stop-motor is still called before the exception escapes unwind-protect.

  (begin (start-motor)

The cleanup forms are evaluated in the same dynamic environment as unwind-protect. If an exception is thrown within cleanup, it will be handled outside of the unwind-protect form.

Although this form looks similar to dynamic-wind, they work at different layers and should not be confused. dynamic-wind is the bottom-level building block and used to manage current exception handlers, current i/o ports, parameters, etc. dynamic-wind’s before and after thunks are called whenever any of those control flow transition occurs. On the other hand, unwind-protect only cares about the Gauche’s exception system. unwind-protect’s cleanup is called only when expr exits normally or throws Gauche’s exception. In the above example, if control escapes from drill-a-hole by calling a continuation captured outside of unwind-protect, cleanup is not called; because the control may return to drill-a-hole again. It can happen if user-level thread system is implemented by call/cc, for example.

The name of this form is taken from Common Lisp. Some Scheme systems have similar macros in different names, such as try-finally.

Function: with-error-handler handler thunk

Makes handler the active error handler and executes thunk. If thunk returns normally, the result(s) will be returned. If an error is signaled during execution of thunk, handler is called with one argument, an exception object representing the error, with the continuation of with-error-handler. That is, with-error-handler returns whatever value(s) handler returns.

If handler signals an error, it will be handled by the handler installed when with-error-handler called.

The dynamic environment where handler is executed is the same as the error occurs. If dynamic-wind is used in thunk, its after method is called after handler has returned, and before with-error-handler returns.

Note: Using this procedure directly is no longer recommended, since guard is more safe and portable. We’ll keep this for a while for the backward compatibility, but we recommend to rewrite code to use guard instead of this. The common idiom of "cleanup on error" code:

(with-error-handler (lambda (e) (cleanup) (raise e))
  (lambda () body …))

should be written like this:

(guard (e [else (cleanup) (raise e)])
  body …)

Behavior of unhandled exception

If an exception is raised where no program-defined exception handler is installed, the following action is taken.

  1. If an unhandled exception occurs within a thread other than the primordial one, it terminates the thread, and the thrown condition is wrapped by <uncaught-exception> condition and stored in the thread object. If other thread calls thread-join! to retrieve result, the the <uncaught-exception> is thrown in that thread. Note that no messages are displayed when the original uncaught exception is thrown. See Thread programming tips, for the details.
  2. Otherwise, if the program is running interactively (in repl), the information of the thrown exception and stack trace are displayed, and the program returns to the toplevel prompt.
  3. If the program is running non-interactively, the information of the thrown exception and stack trace are displayed, then the program exits with an exit status EX_SOFTWARE (70).

The default error message and stack trace in the above case 2 and case 3 is printed by report-error procedure. You can use it in your error handler if you need the same information.

Function: report-error exn :optional sink

Prints type and message of a thrown condition object exn, then print the current stack trace. This is the procedure the system calls when you see an error reported on REPL.

Since you can raise any object, exn can be any object; it’s not needed to be an instance of <condition>. A suitable message is chosen by report-error.

You can specify where the output goes by the optional sink argument: If it is an output port, the output goes there; you can also pass #t for the current output port and #f for the output string port, just like format. That is, when you pass #f, the message goes to a temporary output string port, and gathered string is returned. For all the other cases, an undefined value is returned. If sink is omitted or any other object listed above, the current error port is used.

Note: As of 0.9.5, this procedure prints stack trace of the context where report-error is called, rather than the context where exn is thrown. It doesn’t matter much as far as you call report-error directly inside the error handler, but in general what you want to print is the latter, and we have a plan to attach stack trace info to <condition> object in future.

Low-level exception handling mechanism

This layer provides SRFI-18 compatible simple exception mechanism. You can override the behavior of higher-level constructs such as with-error-handler by using with-exception-handler.

Note that it is a double-edged sword. You’ll get a freedom to construct your own exception handling semantics, but the Gauche system won’t save if something goes wrong. Use these primitives when you want to customize the system’s higher-level semantics or you are porting from other SRFI-18 code.

Function: current-exception-handler

[SRFI-18] Returns the current exception handler.

Function: with-exception-handler handler thunk

[R7RS][SRFI-18] A procedure handler must take one argument. This procedure sets handler to the current exception handler and calls thunk.

Generally, if you want to handle non-continuable exception such as errors using this low-level mechanism, you have to transfer the control from the handler explicitly (See the explanation of with-error-handler above). raise detects if the handler returns on the non-continuable exceptions and reports an error using the default error handler mechanism, but it is just a safety net.

Note also that handler is called in the same dynamic environment of raise. So if you raise an exception inside handler, it is captured by handler again. It is the programmer’s responsibility to propagate the exception handling to the “outer” exception handlers.

The behavior of those procedures can be explained in the following conceptual Scheme code.

;; Conceptual implementation of low-level exception mechanism.
;; Suppose %xh is a list of exception handlers

(define (current-exception-handler) (car %xh))

(define (raise exn)
  (receive r ((car %xh) exn)
    (when (uncontinuable-exception? exn)
      (set! %xh (cdr %xh))
      (raise (make-error "returned from uncontinuable exception")))
    (apply values r)))

(define (with-exception-handler handler thunk)
  (let ((prev %xh))
      (lambda () (set! %xh (cons handler %xh)))
      (lambda () (set! %xh prev)))))

Previous: , Up: Exceptions   [Contents][Index]

6.20.4 Conditions

Built-in Condition classes

Gauche currently has the following hierarchy of built-in condition classes. It approximately reflects SRFI-35 and SRFI-36 condition hierarchy, although they have Gauche-style class names. If there’s a corresponding SRFI condition type, the class has the SRFI name as well.

    +- <compound-condition>
    +- <serious-condition>
    |    +- <serious-compound-condition> ; also inherits <compound-condition>
    +- <message-condition>
         +- <error>                      ; also inherits <serious-condition>
              +- <system-error>
              +- <unhandled-signal-error>
              +- <read-error>
              +- <io-error>
                   +- <port-error>
                        +- <io-read-error>
                        +- <io-write-error>
                        +- <io-closed-error>
                        +- <io-unit-error>

Note that some conditions may occur simultaneously; for example, error during reading from a file because of device failure may consist both <system-error> and <io-read-error>. In such cases, a compound condition is raised. So you can’t just use, for instance, (is-a? obj <io-read-error>) to check if <io-read-error> is thrown. See the "Condition API" section below.

Metaclass: <condition-meta>

Every condition class is an instance of this class. This class defines object-apply so that you can use a condition class as a predicate, e.g.:

(<error> obj) ≡ (condition-has-type? obj <error>)
Class: <condition>
Condition Type: &condition

[SRFI-35] The root class of the condition hierarchy.

Class: <compound-condition>

Represents a compound condition. A compound condition can be created from one or more conditions by make-compound-condition. Don’t use this class directly.

A compound condition returns #t for condition-has-type? if any of the original conditions has the given type.

Class: <serious-condition>
Condition Type: &serious

[SRFI-35] Conditions of this class are for the situations that are too serious to ignore or continue. Particularly, you can safely assume that if you raise this type of condition, it never returns.

Class: <serious-compound-condition>

This is an internal class to represent a compound condition with any of its component condition is serious. Inherits both <compound-condition> and <serious-condition>. make-compound-condition uses this class if the passed conditions includes a serious one. Don’t use this class directly.

Class: <message-condition>
Condition Type: &message

[SRFI-35] This class represents a condition with a message. It has one slot.

Instance Variable of <message-condition>: message

A message.

Class: <error>
Condition Type: &error

[SRFI-35] Indicates an error. Inherits <serious-condition> and <message-condition>, thus has message slot.

Note: SRFI-35 &error condition only inherits &serious and not &message, so you have to use compound condition to attach a message to the error condition. Gauche uses multiple inheritance here, largely because of backward compatibility. To write a portable code, an error condition should be used with a message condition, like this:

  (&message (message "Error message"))
Class: <system-error>

A subclass of <error>. When a system call returns an error, this type of exception is thrown. The message slot usually contains the description of the error (like the one from strerror(3)). Besides that, this class has one more instance slot:

Instance Variable of <system-error>: errno

Contains an integer value of system’s error number.

Error numbers may differ among systems. Gauche defines constants for typical Unix error values (e.g. EACCES, EBADF, etc), so it is desirable to use them instead of literal numbers. See the description of sys-strerror in System inquiry for available constants.

This class doesn’t have corresponding SRFI condition type, but important to obtain OS’s raw error code. In some cases, this type of condition is compounded with other condition types, like <io-read-error>.

Class: <unhandled-signal-error>

A subclass of <error>. The default handler of most of signals raises this condition. See Handling signals for the details.

Instance Variable of <unhandled-signal-error>: signal

An integer indicating the received signal number. There are constants defined for typical signal numbers; see Signals and signal sets.

Class: <read-error>
Condition Type: &read-error

[SRFI-36] A subclass of <error>. When the reader detects a lexical or syntactic error during reading an S-expression, this type of condition is raised.

Instance Variable of <read-error>: port

A port from which the reader is reading. (NB: SRFI-36’s &read-error doesn’t have this slot. Portable program shouldn’t rely on this slot).

Instance Variable of <read-error>: line

A line count (1-base) of the input where the reader raised this error. It may be -1 if the reader is reading from a port that doesn’t keep track of line count.

Instance Variable of <read-error>: column
Instance Variable of <read-error>: position
Instance Variable of <read-error>: span

These slots are defined in SRFI-36’s &read-error. For the time being, these slots always hold #f.

Class: <io-error>
Condition Type: &io-error

[SRFI-36] A base class of I/O errors. Inherits <error>.

Class: <port-error>
Condition Type: &io-port-error

[SRFI-36] An I/O error related to a port. Inherits <io-error>.

Instance Variable of <port-error>: port

Holds the port where the error occurred.

Class: <io-read-error>
Condition Type: &io-read-error

[SRFI-36] An I/O error during reading from a port. Inherits <port-error>.

Class: <io-write-error>
Condition Type: &io-write-error

[SRFI-36] An I/O error during writing to a port. Inherits <port-error>.

Class: <io-closed-error>
Condition Type: &io-closed-error

[SRFI-36] An I/O error when read/write is attempted on a closed port. Inherits <port-error>.

Class: <io-unit-error>

An I/O error when the read/write is requested with a unit that is not supported by the port (e.g. a binary I/O is requested on a character-only port). Inherits <port-error>.

Condition API

Macro: define-condition-type name supertype predicate field-spec …

[SRFI-35+] Defines a new condition type. In Gauche, a condition type is a class, whose metaclass is <condition-meta>.

Name becomes the name of the new type, and also the variable of that name is bound to the created condition type. Supertype is the name of the supertype (direct superclass) of this condition type. A condition type must inherit from <condition> or its descendants. (Multiple inheritance can’t be specified by this form, and generally should be avoided in condition type hierarchy. Instead, you can use compound conditions, which don’t introduce multiple inheritance.)

A variable predicate is bound to a predicate procedure for this condition type.

Each field-spec is a form of (field-name accessor-name), and the condition will have fields named by field-name, and a variable accessor-name will be bound to a procedure that accesses the field. In Gauche, each field becomes a slot of the created class.

Gauche extends srfi-35 to allow predicate and/or accessor-name to be #f, or accessor-name to be omitted, if you don’t need to them to be defined.

When define-condition-type is expanded into a class definition, each slot gets a :init-keyword slot option with the keyword whose name is the same as the slot name.

Function: condition-type? obj

[SRFI-35] Returns #t iff obj is a condition type. In Gauche, it means (is-a? obj <condition-meta>).

Function: make-condition-type name parent field-names

[SRFI-35] A procedural version to create a new condition type.

Function: make-condition type field-name value …

[SRFI-35] Creates a new condition of condition-type type, and initializes its fields as specified by field-name and value pairs Returns #t iff obj is a condition. In Gauche, it means (is-a? obj <condition>).

Function: condition-has-type? obj type

[SRFI-35] Returns #t iff obj belongs to a condition type type. Because of compound conditions, this is not equivalent to is-a?.

Function: condition-ref condition field-name

[SRFI-35] Retrieves the value of field field-name of condition. If condition is a compound condition, you can access to the field of its original conditions; if more than one original condition have field-name, the first one passed to make-compound-condition has precedence.

You can use slot-ref and/or ref to access to the field of conditions; compound conditions define a slot-missing method so that slot-ref behaves as if the compound conditions have all the slots of the original conditions. Using condition-ref increases portability, though.

Function: make-compound-condition condition0 condition1 …

[SRFI-35] Returns a compound condition that has all condition0 condition1 …. The returned condition’s fields are the union of all the fields of given conditions; if any conditions have the same name of fields, the first one takes precedence. The returned condition also has condition-type of all the types of given conditions. (This is not a multiple inheritance. See <compound-condition> above.)

Function: extract-condition condition condition-type

[SRFI-35] Condition must be a condition and have type condition-type. This procedure returns a condition of condition-type, with field values extracted from condition.

Macro: condition type-field-binding …

[SRFI-35] A convenience macro to create a (possibly compound) condition. Type-field-binding is a form of (condition-type (field-name value-expr) …).

  (type0 (field00 value00) ...)
  (type1 (field10 value10) ...)
  (make-condition type0 'field00 value00 ...)
  (make-condition type1 'field10 value10 ...)

Previous: , Up: Exceptions   [Contents][Index]