This and other RFCs are available on the web at http://dev.perl.org/rfc/ =head1 TITLE Structured Exception Handling Mechanism (Try) =head1 VERSION Maintainer: Tony Olekshy <[EMAIL PROTECTED]> Date: 08 Aug 2000 Version: 1 Mailing List: [EMAIL PROTECTED] Number: 88 =head1 ABSTRACT This RFC considers the addition to Perl of the exception handling verbs B<try>, B<throw>, B<except>, B<catch>, B<finally>, and B<unwind>. Its exception handling mechanism can be extended without adding new verbs. It also introduces the concept of exception stack handling while unwinding. This RFC does not constrain the details of exception objects per se, but it does define the operations that may be performed on such objects by the new verbs. A zip of Try.pm, a Perl 5 implementation of the functionality described hereunder, complete with a set of regression tests, is available at http://www.avrasoft.com/perl/rfc/try-1136.zip To exercise the regression tests, run the "regress" script. =head1 DESCRIPTION Various implementations of exception handling mechanisms for Perl have (over the years) used eval, die, $SIG{__DIE__}, and $@ in incompatible ways. This has lead to a legacy situation that is impeding the development of robust modules, frameworks, and applications. In addition, all of Perl's extant exception handling modules (to the author's knowledge) overwrite the information on the exception they are processing whenever an exception is raised during said processing. Therefore, during exception handling, no information is available on any but the last of the set of exceptions being handled during the current unwinding. There is no way to ask, "Was exception Foo part of what's going on?" Loosing track of exceptions while processing them also means that should some client need to be informed of the exceptions being processed, only one level of context information is available, such as: "Can't add a new person to the database." However, if the set of exceptions being processed is available, one can arrange to get information like this: UIM.1234: Can't add a new person to the database. APP.2345: Can't update Company relationship. DBM.3456: Trouble processing SQL UPDATE clause. DBM.4567: Unable to write to Company table. IOM.5678: Can't open file ".../company.db". IOM.6789: Access to ".../company.db" denied. The Try.pm module described hereunder provides a solution to the procedural part of the structured exception handling problem, that is, to the syntax and semantics of verbs like try, throw, catch, and finally. As far as Try is concerned, the nouns involved (the actual exceptions passed to throw or die) can be any Perl datum. A valid try statement looks like this: try { ... throw ... } # try clause except TEST => catch { ... } # catch clause finally { ... } # finally clause unwind { ... }; # unwind clause There can be zero or more catch clauses and finally clauses; all finally clauses must come after any catch clauses. There can be zero or one unwind clause. The "except TEST" part of a catch clause is optional. The available TESTs are disussed later hereunder. The clauses are evaluated in left-to-right order. Try traps and keeps track of any exceptions raised by throw or die under any clause, and proceeds as follows: * The try clause is evaluated. * Each catch clause is invoked, but only if an exception has been raised since the beginning of the try statement, and the catch clause's except TEST (if any) is true. * Each finally clause is invoked whether or not an exception has been raised since the beginning of the try statement. * The unwind clause, if any, is invoked if an exception has been raised since the beginning of the try statement, and it has not been cleanly caught. * After processing all clauses, try unwinds (dies) iff any exception wasn't cleanly caught. Here are five simple examples that illustrate some of the core functionality implemented by Try. =over 4 =item * Catch All Exceptions use Try; try { ... } catch { ... }; =item * Catch Exceptions by Message Text my ($x, $y, $z) = ( ... ); try { $x = $y / $z; } except "division by 0" => catch { $x = undef; }; =item * Catch Exceptions by Class try { structured_io_operation; } except isa => "IO:Error" => catch { ... }; =item * Clean Up Even If Unwinding my $o; try { f( $o = SomeClass->New ); } finally { $o and $o->Done; }; =item * Handle Complex Nested Trys try { try { throw "First"; } finally { throw "Second"; } } except First => catch { print "Caught First\n"; } except Second => catch { print "Caught Second\n"; }; except else => catch { throw "Something blew up."; }; (Note that this case can't be handled without an unwind stack, because throw "Second" overwrites exception "First", so except First doesn't match.) =back Try's mechanism works with exceptions raised via either throw or die. Although a minimalist exception object base class (Try::Except) is provided as part of the Try module, you are free to concurrently use your own exception classes with Try, or to simply use string exceptions, including those generated by Perl's "die" function. This approach respects Perl's TMTOWTDI tradition. The factoring of this problem into its nouns and verbs is useful because of the way it allows Try to handle multiple independent exception class hierarchies. The only operations Try performs on exception nouns are stringification and isa. Nevertheless, via "except TEST" expressions, Try provides a mechanism by which any other first-class exception object methods can be used as the basis for complex exception handling. Try's mechanism can be used without any reference to eval, die, $@, or $SIG{__DIE__} on the client side of its API, so the addition to Perl of a mechanism like Try could leave those constructs unchanged, to the benefit of legacy applications. In addition, until Perl has a built-in explicit exception handling mechanism, Try's working implementation can be used instead (since it interacts reasonably well with Perl's legacy constructs). =head1 PRINCIPLES OF OPERATION =head2 Statement Syntax Try installs six subroutines into the package from which it is used, namely: try, throw, except, catch, finally, and unwind. The try, catch, finally, and unwind subroutines are prototyped as &@, so you can write "try { ... }" instead of having to write "try sub { ... }". The complete syntax of a valid try "statement" is as follows, where ? is zero-or-one, * is zero-or-more, and + is one-or-more. <try> := try { ... } <catch-clause>* <finally-clause>* <unwind-clause>? ; <throw> := throw <exception> { ... => ... }? ; <exception> := <object> | <string>+ <catch-clause> := catch { ... } | except catch { ... } | except <test> => catch { ... } <finally-clause> := finally { ... } <unwind-clause> := unwind { ... } <test> := isa => <string> | any => sub { ... } | else | check | sub { ... } | <re> | <string> When parsing <test>s, the first keyword match found (in the order shown) wins, and the following arguments are parsed according to it. <string> The next scalar value in try's argument list, which is stringified. <string>+ means a list of comma-seperated strings. <object> A blessed reference. <re> A compiled regex, typically of the form qr/.../ { ... } The body of a closure. { ... => ... } A hash reference of named option values. =head2 Statement Semantics The except, catch, finally, and unwind subroutines wrap up their arguments to build a list for try. The try subroutine processes said list while handling exceptions and unwinding. The Try module keeps (in module-scoped lexical variables) a shadow unwind stack and some other state information. The throw subroutine raises an exception, very much like die (but it knows one extra thing: it's not a legacy die invocation). A throw with no arguments just unwinds without stacking another exception. If the Debug option is specified in an invocation of throw, its value is tracked on a parallel debug stack, which may be used by Try::ShowStack to show additional context information. The try subroutine localizes and undefs $SIG{__DIE__}, evals its closure, and post-processes $@ and Try's state information to push a raised exception, if any, onto the unwind stack. The try subroutine returns the last value in its closure (if it doesn't unwind). Any catch and finally clauses are processed, in order, before try returns. The closures of catch clauses are only invoked if an exception has been raised since the beginning of the try statement, and the catch clause's except TEST (if any) evaluates to true (as described below). Each finally clause is invoked whether or not an exception has been raised since the beginning of the try statement. The unwind clause, if any, is invoked only if an exception has been raised and not cleanly handled. The unwind clause is provided to allow you to collapse two nested try clauses when the only purpose of the outer clause is to catch unwinding from the inner clause no matter how it failed. For example: try { TryToFoo; } catch { TryToHandleFailure; } finally { TryToCleanUp; } unwind { throw "Can't cleanly Foo."; }; Exceptions raised in try, catch, finally, and unwind clauses, and in TESTs, are trapped by Try, are tracked on its unwind stack, and are available to the except TEST expressions. After processing all clauses, try unwinds (dies) iff the processing of any clause raised an exception, unless it was cleanly caught. Cleanly caught means the exception was in the try clause, and it triggered a catch clause, and no catch or finally clause raised an exception. If try does not unwind, it clears the unwind stack. Whenever throw or try unwinds, the argument it passes to die is a string-formatted version of the unwind stack. Therefore, while unwinding, $@ is not "", and if we don't end up handling the exception somewhere, Perl (or someone else's $SIG{__DIE__}) will see something useful in the contents of $@. While processing its argument list, the try subroutine performs sanity checks on the values it has received. For example, try { } except isa => "Class" => finally { }; stacks the following exception and unwinds: TRY.1030: Expecting "catch" clause but found "finally". =head2 Exception Tests The Try module supports a number of except TEST expressions that can be used to select which catch clauses are to be invoked when an exception has been raised. These tests have been selected to support Perl's DWIM tradition, and to make easy easy and hard possible. Each type of qualified catch clause is shown here in context. catch { ... } This clause is invoked if the unwind stack is not empty. It is typically used, in this unadorned form, to catch and handle all possible exceptions, as in: try { } catch { print Try::ShowStack; }; except "..." => catch { ... } This clause is invoked if there is an item on the unwind stack which, when stringified, matches /\Q...\E/, for example: try { $x = $y / $z; } except "division by 0" => catch { $x = undef; }; To search for a word of the form /^[_a-z][_a-z0-9]*$/ (that is, a lower case identifier) you must use a TEST of the form except qr/.../ => catch { ... }, as described below, because all such words are reserved for current or future use in except TEST expressions. An exception will be raised if you don't. except qr/.../ => catch { ... } This clause is invoked if there is an item on the unwind stack which, when stringified, matches /.../, for example: try { $x = $y / $z; } except qr/div.* by 0/ => catch { $x = undef; }; except isa => "ClassName" => catch { ... } This clause is invoked if there is an item on the unwind stack which is a reference and which passes the test $item->isa("ClassName"), for example: try { structured_io_operation; } except isa => "IO:Error" => catch { ... }; except any => sub { ... } => catch { ... } The sub closure is invoked for each object on the unwind stack, passing said object in as $_[0]. As soon as an object is found for which the sub returns true, the catch closure is invoked and the clause is done. If no object satisfies the sub closure, the catch closure is not invoked. This type of catch clause can be used to invoke arbitrary test methods on stacked exception objects, as in: try { } except any => sub { $_[0]->isa("Foo") && $_[0]->SomeTest } => catch { }; In addition, a literal or variable reference to a named or anonymous closure can be used instead of sub { }, as in: try { } except any => \&MyTest => catch { }; except sub { ... } => catch { ... } The sub closure is invoked, and if it returns true the catch closure is invoked. The sub closure is passed a copy of the current unwind stack in @_. This provides a generic way to perform tests against the state of the world, as in: try { } except sub { f( @_ ) } => catch { }; In addition, a literal or variable reference to a named or anonymous closure can be used instead of sub { }, as in: try { } except \&MyTest => catch { }; except check => catch { ... } The catch closure is invoked if the unwind stack is not empty. It is passed a copy of the unwind stack in @_. If the closure returns true (without raising an exception), then the exception is considered to be caught (for the purposes of unwinding) otherwise it is not. This form of except TEST expression can be used when it is not convenient to seperate the test for an exception from the catching thereof, as in: try { } except check => catch { f( @_ ) ? 1 : 0 }; except else => catch { ... } If the try clause threw, and no catch clause before the except else clause has been triggered and successfully completed, then the catch closure is invoked. This provides a convenient way to say things like: try { } except "something known" => catch { ... } except else => catch { something_unknown; }; =head2 Exception Objects The throw subroutine accepts any Perl datum as an exception, including a reference to something blessed into a descendent of UNIVERSAL. In the latter case, an except isa => "ClassName" expression can be used to select relevent catch clauses. Objects passed via die (in recent versions of Perl) are also handled as objects. The Try module contains a minimal exception object base class called Try::Except. You can use it as is. You can derive your own classes from it. You can also ignore it--nothing else in Try cares, and it doesn't pollute the namespace. Try::Except objects have one publically visible Message instance variable. Try::Except->New takes an argument and sets said ivar to it. Stringification returns said ivar. A Throw constructor is also provided for your convenience, it just does: throw( $_[0]->New($_[1]) ); By design, you can implement your own exception class hierarchy and use it with Try's structured exception handling mechanism. The only operations performed on your objects are stringification and isa. Your classes will play well with other exception classes that define a useful stringification, and with unadorned dies (including those in legacy modules and the Perl core). Internally, Try actually converts non-object exceptions into instances of the following classes, which you can test against, or (because of stringification) you can ignore. These classes are all derivatives of Try::Except. Try::Except::Error Trouble messages relating to try's syntax are wrapped up as objects of this class. Try::Except::Throw If throw is passed a non-object exception, it is stringified and wrapped up as an object of this class. Try::Except::Die If Try detects that an exception was raised by die rather than by throw, and said exception is not already an object, then the exception is stringified and wrapped up as an object of this class. =head2 Unwind State Accessors In application and framework environments it is normal to wrap user events in an outer try that looks like this: try { handle_user_event; } catch { show_trouble( Try::ShowStack ); }; The Try::ShowStack subroutine returns a string-formatted copy of the current unwind stack suitable for printing. For example: try { try { try { throw "TST.1001: First trouble."; } finally { throw "TST.1002: Second trouble.", {Debug => "Debug Second."}; } } except First => catch { throw "TST.1003: First catch trouble."; } except Second => catch { throw "TST.1004: Second catch trouble."; }; } catch { print Try::ShowStack; }; prints: TST.1004: Second catch trouble. TST.1003: First catch trouble. TST.1002: Second trouble. TST.1001: First trouble. Try::ShowStack takes the following optional parameters: Label => 1 If set the formatted unwind stack is annotated with the classes of the objects therein, as in: Try::Except::Throw: TST.1004: Second catch trouble. Try::Except::Throw: TST.1003: First catch trouble. Try::Except::Throw: TST.1002: Second trouble. Try::Except::Throw: TST.1001: First trouble. Trace => 1 If set the value returned by ShowStack includes the Perl stack traceback as at the last leaf-level throw (that is, a throw or die when the unwind stack is empty), as in: TST.1004: Second catch trouble. TST.1003: First catch trouble. TST.1002: Second trouble. TST.1001: First trouble. Try::throw called from test-403.pl[7]. Try::try called from test-403.pl[9]. Try::try called from test-403.pl[11]. Try::try called from test-403.pl[14]. Debug => 1 Annotates entries in the unwind stack with the values from the Debug option in the throw statements, if any. For example: TST.1004: Second catch trouble. TST.1003: First catch trouble. TST.1002: Second trouble. Context: Debug Second. TST.1001: First trouble. The following package variables also affect the output produced by Try::ShowStack. $Try::Debug = 1 Shows all subroutines in Perl's stack traceback, including those internal to Try itself, as in: TST.1004: Second catch trouble. TST.1003: First catch trouble. TST.1002: Second trouble. TST.1001: First trouble. Try::__ANON__ called from Try.pm[384]. Try::throw called from test-403.pl[7]. main::__ANON__ called from Try.pm[134]. (eval) called from Try.pm[134]. Try::__ANON__ called from Try.pm[157]. Try::try called from test-403.pl[9]. main::__ANON__ called from Try.pm[134]. (eval) called from Try.pm[134]. Try::__ANON__ called from Try.pm[157]. Try::try called from test-403.pl[11]. main::__ANON__ called from Try.pm[134]. (eval) called from Try.pm[134]. Try::__ANON__ called from Try.pm[157]. Try::try called from test-403.pl[14]. $Try::Devel = 1 Attempts to use the Devel::DumpStack module to create a Perl stack traceback that includes subroutine argument values, as in: TST.1004: Second catch trouble. TST.1003: First catch trouble. TST.1002: Second trouble. TST.1001: First trouble. $ = Try::throw('TST.1001: First trouble.') called from test-403.pl[7]. $ = Try::try(CODE(0xca9054), 'finally', CODE(0x10b7118)) called from test-403.pl[9]. $ = Try::try(CODE(0xca9114), 'except', 'First', 'catch', CODE(0x10b7650), 'except', 'Second', 'catch', CODE(0x10b76bc)) called from test-403.pl[1]. $ = Try::try(CODE(0xcafc94), 'catch', CODE(0x10b7764)) called from test-403.pl[4]. In addition to Try::ShowStack, Try provides three other accessor functions for internal state information. Try::UnwindStack provides a copy of a list of all the exceptions on the unwind stack, most recent first. Try::DebugStack does the same for the values of Debug options passed to throws. Try::TraceBack provides a string formatted version of the Perl stack traceback as at the time the first exception occured. These accessors can be used in except TEST expressions, and they can be used to implement a custom Try::ShowStack (such as not showing debug and stack traceback info to the end user, but logging it for admins). =head1 IMPLEMENTATION The Try module approach could provide an evolutionary path via which Perl 5 programs can grow to use the new Perl 6 mechanism before Perl 6 is ready, via the Perl 5 implementation of Try.pm. Adding structured exception handling to the core would provide a clean set of verbs for functionality that is currently implemented via ad hoc uses of eval, die, $@, and $SIG{__DIE__}. Also, changes to the internal use of exception objects, by Perl, can be coordinated with the new verbs. Since Try doesn't expose eval, die, $@, or $SIG{__DIE__}, Perl 6 could make those mechanisims invisible to Perl programs, but use them (or something like them) internally, to implement structured exception handling. This provides a way to grow out of the current incompatible uses of eval, die, $@, and $SIG{__DIE__}. Adding structured exception handling to the core would also result in run-time speedups, since Try's clause-list-building and handling would not have to be done in run-time Perl. Finally, as core functionality, a sanctioned structured exception handling mechanism can be the place where Perl defines the standard requirements for compatible exception object classes. Alternatively, in the name of I<Perl should stay Perl>, Perl 6 could just throw out some of the worst legacy abuses (such as $SIG{__DIE__}), and Try.pm could remain a module (or, a core module). In this case all that is required is to make sure that Try.pm can be implemented in Perl 6. =head1 ISSUES If Try's notions of the seperation of exception handling from the implementation of exception objects gains credence, there will need to be one or more RFCs on the matter of the built-in or core-module Exception class sanctioned by Perl 6. For example, the exception texts used herein often contain a source-code-locator like ABC.1234. The author would like to see some such functionality make it into a core base class for Exception objects. =head1 ACKNOWLEDGEMENTS The current implementation of Try has been refined by studying Graham Barr's C<Error.pm> module, via discussions on the perl-friends mailing list, through use by the staff of Avra Software Lab Inc., and through discussion at the Software Engineering Research Lab at the University of Alberta. Previous versions of structured exception handling with unwind stack functionality have been developed by Mr. Olekshy in Scheme, C++, Visual Objects, and Delphi. =head1 REFERENCES =over 4 =item * A zip of Try.pm, a Perl 5 implementation of the functionality described hereunder, complete with a set of regression tests, is available at http://www.avrasoft.com/perl/rfc/try-1136.zip To exercise the regression tests, run the "regress" script. =back =head1 REVISIONS =over 4 =item * Version 1, 2000-08-08 Based on Avra's Try.pm redaction 1.1.3.6. =back
