TIP 329: Try/Catch/Finally syntax

Login
Bounty program for improvements to Tcl and certain Tcl packages.
Author:         Trevor Davel <twylite@crypt.co.za>
State:          Final
Type:           Project
Vote:           Done
Created:        22-Sep-2008
Post-History:   
Discussions-To: http://wiki.tcl.tk/21608
Obsoletes:      89
Tcl-Version:    8.6

Abstract

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

Rationale

See [89] for general rationale for enhancing exception handling.

The try syntax presented here is not intended to replace catch, but to simplify the expression of existing exception/error handling techniques, leading to greater code clarity and less error-prone workarounds for finally blocks. There is no deficiency in the functionality of Tcl's exception handling mechanisms - what is lacking is a more readable syntax and a standard for behaviour across packages for the common case of catching a subset errors or exceptions that are thrown from within a particular block of code.

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. [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 [90] which extends catch to allow an additional dictionary of options (error information) to be captured. These options supercede the ::errorInfo and ::errorCode globals (though those are still supported for backward compatibility). It is therefore logical to extend/correct the syntax of [89] to support the options dictionary in preference to the older mechanism for capturing exception information.

Benefits of adding this functionality to the core:

  • Bring to Tcl a construct commonly understood and widely used in other languages.

  • A standard for identifying categories/classes of errors, which will improve interoperability between packages.

  • A byte-coded implementation would be significantly faster than the Tcl implementation that is presented.

Specification

try body ?handler ...? ?finally body?

throw type message

The try body is evaluated in the caller's scope. The handlers are searched in order of declaration until a matching one is found, and the associated body is executed. If no matching handler is found then try returns the result of the try body (exceptions will propagate up the stack as usual); otherwise try returns the result of the handler body (exceptions will propagate up the stack as usual).

Only one handler body (that of the first matching handler) will be executed. If the handler body is the literal string "-" then the body for the subsequent handler will be used instead. It is an error for the last handler's body to be a literal "-".

The finally body (if present) will be executed last, and is always executed whatever the results of the try and handler bodies (excepting resource exhaustion or cancellation). If the finally body returns an exceptional code then this will become the result of try, otherwise the result of the finally body is ignored.

Since the trap handlers in the try control structure are filtered 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. type is treated as a list by trap (see below), which maintains compatibility with the description of ::errorCode given in tclvars.

Handlers

Each handler is identified by a keyword. The fields following the keyword indicate what exceptions or errors are matched by the handler, the variables into which the result of the try body will be assigned (in the caller's scope), and the body of the handler.

on code {?resultVar ?optionsVar?} body

The on handler allows exact matching against the exceptional return code (the integer value that would be returned by catch). The code may be given as an integer or one of the magic keywords ok (0), error (1), return (2), break (3), continue (4).

trap pattern {?resultVar ?optionsVar?} body

The trap handler allows list prefix matching against the -errorcode from the options when the exceptional return code is TCL_ERROR (1). Given a pattern and an errorcode, a list prefix match is successful if for every element in pattern there is a corresponding and identical element in errorcode. Trailing elements in errorcode are ignored.

Notes & clarifications:

  • Handlers are searched in order of declaration (left-to-right). One consequence of this search order is that an on error handler will supercede all subsequent trap handlers.

  • Any unhandled exception propagates.

  • The result of the last executed body (other than the finally body) is the result of the try. Exceptions in any handler body or in the finally replace the existing exception and propagate.

  • If any exception is replaced (by an exception in a handler body or in the finally body) then the new exception shall introduce into its options dictionary the field -during that contains the options dict of the exception that was replaced.

  • If any errorcode happens to be not a list, a trap handler will be unable to process it. However, this should only happen in cases where there is a bug or other problem elsewhere, since return is documented to require the errorcode to be a list.

Examples

Simple example of try/handler/finally logic in Tcl using currently available syntax:

 proc read_hex_file {fname} {
    set f [open $fname "r"]
    set data {}
    set code [catch {
       while { [gets $f line] >= 0 } {
          append data [binary format H* $line]
       }
    } em opts]
    if { $code != 0 } {
       dict set opts -code 1
       set em "Could not process file '$fname': $em"
    }
    close $f
    return -options $opts $em
 }

And the same example rewritten to use [try]:

 proc read_hex_file {fname} {
    set f [open $fname "r"]
    set data {}
    try  {
       while { [gets $f line] >= 0 } {
         append data [binary format H* $line]
       }
    } trap {POSIX} {} {
       puts "POSIX-type error"
    } on error {em} {
       error "Could not process file '$fname': $em"
    } finally {
       close $f
    }
 }

This illustrates how the intent of the code is more clearly expressed by [try].

References

Rejected Alternatives

Various alternatives are discussed on the wiki http://wiki.tcl.tk/21608 along with reasons for their rejection.

Future Extensions

No specific future exceptions are planned, but try could be extended by adding new handler keywords and/or introducing new varnames to the variables list that is associated with each handler.

It is recommended that new handlers maintain the established convention:

keyword criteria {?resultVar ?optionsVar?} body

Reference Implementation

A reference implementation can be found at http://www.crypt.co.za/pub/try-1.tcl

Thanks

Thanks in particular to DKF, NEM and JE for their feedback and suggestions on this TIP.

Copyright

This document has been placed in the public domain.

History