Version 7 of TIP proposal for Try-Catch Exception Handling

Updated 2008-09-12 17:51:24 by nem

TIP proposal: Try/Catch Exception Handling

Draft proposal by Twylite 2008-09-12.

Abstract

This TIP proposes the addition of new core commands to improve the exception handling mechanism. It supercedes TIP #89 by providing support for the error options dict introduced in Tcl 8.5 by TIP #90.

Rationale

See TIP #89 for general rationale for enhancing exception handling.

In Tcl 8.4 exceptions could be caught using [catch], and exception information was available via the [catch] return value and resultvar. If the return value was TCL_ERROR (1) then the globals ::errorCode and ::errorInfo would be set according to the exception raised.

TIP #89 was written to work with this model, such that a catch handler (in a try...catch) would be able to capture the resultvar, errorCode and errorInfo.

Tcl 8.5 implements TIP #90 which extends [catch] to allow an additional dict of options (error information) to be captured. These options supercede the ::errorInfo and ::errorCode globals.

It it logical to extend/correct the syntax of TIP #89 to support the options dict in preference to the older mechanism for capturing exception information.

Specification

  • throw type message

Since the catch handlers in the try...catch control structure will filter based on the exception's errorcode, it makes sense to have a command that will encourage the use of error codes when throwing an exception. [throw] is merely a reordering of the arguments of the [error] command.

  • rethrow ?type? ?message?

TBD Use within a catch handler to rethrow the existing exception, possibly with translation of the errocode and message. The intent is to allow exception translation/chaining without losing the context of the original error.

  • try body ?catch {type ?emvar? ?optvar?} body? ?...? ?finally body?

The try body is evaluated in the caller's scope. If the result is TCL_ERROR then each catch handler is considered in order until one is found with a type that matches the exception's errorcode, then the body of that handler is executed.

Rules:

  • The type is a glob that is used to match against the exception's errorcode.
  • Only one catch handler will be executed. If the type matches for more than one handler then on the first handler (reading left-to-right in the command) will be executed.
  • If no matching handler is found then the exception will propagate up the stack.
  • If the catch body is a "-" then the body of the following catch block will be executed instead.
  • When the handler body is executed the error message will be stored in the emvar (if specified) and the error options in the optvar (if specified).
  • If an exception occurs in a catch block and the errorcode is "RETHROW" then the existing exception is re-raised at this point. (TBD FIXME - this prevents errorcode translation)
  • If an exception other than a rethrow occurs in a catch block then the new exception takes precedence and will propagate upwards, but the existing error stack will be maintained (with an indication that the cause of the error has changed, e.g. "new error X WHILE old error Y"

For consideration

  • Execute ALL matching catch handlers (use break or continue for control?)
  • If there is an exception in a catch handler it takes precedence, but keep looking for other matching handlers
  • If the last handler has type {*} and at least one handler has been executed then don't run the last (catchall) handler
  • Alternative syntax for [finally]
  • What should [try] return? Nothing (like for, while, foreach) or the result of the body (like if and switch)?

References

  • TIP #89 [L1 ]
  • TIP #90 [L2 ]
  • Tcl 8.4 catch [L3 ]

Discussion

KD: What I'm missing in this is that Tcl has already a mechanism for throwing exceptions, namely special return codes. For example, the following throws an exception with return code 7:

 return -code 7 "User provided incomplete input"

which can be catched somewhere on the call tree with:

 switch [catch {do something} result] {
     0 {do the next thing}
     1 {return -code 1 -errorcode $::errorCode -errorinfo $::errorInfo $result}
     7 {tk_messageBox -type ok -icon error -message $result}
 }

Tip 89 seems to use ::errorCode for everything, but exceptions are not errors, and currently ::errorCode in Tcl is used only for I/O and OS errors. To me it seems better to keep return code 1 for these errors, and use special return codes for user-defined exceptions.

NEM: Should try be able to catch non-error exceptions (e.g. break/continue)? Should throw be able to create them? Throw and rethrow need an options dict argument, or take options separately (e.g. -errorinfo). Is type considered to be a string or a list? An exception in a catch handler should abort with error immediately -- don't keep searching through other handlers, as they might throw errors too. I'd say execute only first matching catch handler. Given that type matching is glob-style, the catch handler may also want to capture it so it can determine what exact errorCode matched. Drop the RETHROW errorCode idea - I can't see a good use-case for this. try should return the result of its body. If an error is thrown and caught, then the result of the corresponding catch block should be returned, allowing catch blocks to implement defaults.

Finally, my ideal would be that try/catch creates a lambda(s) for catch handlers and these are invoked at the point of error, rather than when the stack has unwound, as this allows for resumes (via break/ continue). This isn't possible to do for errors in general, however (e.g. C code errors).