=head1 TITLE Omnibus Structured Exception/Error Handling Mechanism =head1 VERSION Maintainer: Tony Olekshy Date: 8 Aug 2000 Last Modified: 1 Oct 2000 Mailing List: perl6-language-errors@perl.org Number: 88 Version: 3 Status: Frozen =head1 NOTES RFC 88 as HTML http://www.avrasoft.com/perl6/rfc88.htm RFC 88 as Text http://www.avrasoft.com/perl6/rfc88.txt RFC 88 as POD http://www.avrasoft.com/perl6/rfc88-pod.txt Perl 5 Try.pm http://www.avrasoft.com/perl6/try6-ref5.txt Regression Test http://www.avrasoft.com/perl6/try-tests.htm =head1 ABSTRACT "The Encyclopedia of Software Engineering" [ESE-1994] says (p.847): =over 4 =item Z<> Inevitably, no matter how carefully a programmer behaves when writing a program and no matter how thoroughly its verification is carried out, errors remain in the program and program execution may result in a failure. [...] The programming language may provide a framework for detecting and then handling faults, so that the program either fails gracefully or continues to work after some remedial action has been taken to recover from the error. Such a linguistic framework is usually called exception handling. =back The I described in this RFC satisfies the following requirements. =over 4 =item 1 It does not, by default, interfere with traditional Perl programming styles. When explicitly used, it simply adds functionality to better support other programming styles, which currently have to undertake some contortions to get the desired effect. Z<> =item 1 It is simple in the cases in which it is commonly used. Z<> =item 1 It is capable enough to handle the needs of production applications, frameworks, and modules, including the ability to hook into the mechanism itself. Z<> =item 1 It is suitable for "error" handling via exceptions. Z<> =item 1 It is suitable for light-weight exceptions that may not involve errors at all. =back This RFC describes a collection of changes and additions to Perl, which together support a built-in base class for Exception objects, and exception/error handling code like this: exception 'Alarm'; try { throw Alarm "a message", tag => "ABC.1234", ... ; } catch Alarm => { ... } catch Error::DB, Error::IO => { ... } catch $@ =~ /divide by 0/ => { ... } catch { ... } finally { ... } Any exceptions that are raised within an enclosing try, catch, or finally block, where the enclosing block can be located anywhere up the subroutine call stack, are trapped and processed according to the semantics described in this RFC. The new built-in Exception base class is designed to be used by Perl for raising exceptions for failed operators or functions, but this RFC can be used with the Exception base class whether or not that happens. Readers who are not familiar with the technique of using exception handling to handle errors should refer to the L section of this document first. It is not the intent of this RFC to interfere with traditional Perl scripts; the intent is only to facilitate the availability of a more controllable, pragmatic, and yet robust mechanism when such is found to be appropriate. =over 4 =item * Nothing in this RFC impacts the tradition of simple Perl scripts. =item * C continues to work as before. =item * There is no need to use C, C, C, or C at all, if one doesn't want to. =item * This RFC does not require core Perl functions to use exceptions for signalling errors. =back =head1 DEFINITIONS B =over 4 =item Z<> An exception is raised to begin call-stack unwinding according to the semantics described herein. This provides for controlled non-local flow-control. This is what C does. =back B =over 4 =item Z<> The passing of an exception up the call stack for further processing is called propagation. Raising an exception starts propagation. Propagation stops when the exception is trapped. =back B =over 4 =item Z<> The overall process of handling the propagation of an exception, from the point it is raised until the point it is trapped, is called unwinding. =back B =over 4 =item Z<> The termination of unwinding, for the purpose of attempting further processing using local flow-control semantics, is called trapping. This is what C does, as do C, C, and C. =back B =over 4 =item Z<> This means the trapping and handling of an exception did not itself raise an exception. Cleanly handling exceptions raised while handling exceptions is difficult, tedious, and error-prone given only C. =back B =over 4 =item Z<> An exception is a collection of informaton about a particular non-local goto, captured at raise-time, for use by trap-time handling based on said informaton. This is what C<$@> is. It can be any structured data; a reference to a blessed hash works well in practice, uses minimal OO concepts, and can be made to stringify just like the Perl 5 C<$@>. =back B =over 4 =item Z<> A fuzzy concept, an error is essentially an exception with a negative connotation. A traditional definition might be, "an exception raised to signal an assertion failure that typically indicates the inability of an algorithm to complete the request it has been given". Ultimately, whether or not an exception is considered to be an error depends on the trapper, not the raiser. =back =head1 DESCRIPTION The most common forms of structured exception handling are straight- forward. Here they are: try { ... } catch { ... } =over 4 =item Z<> Invoke the catch block only if the try block raises an exception (aka dies), otherwise, there is nothing to catch. Continue unwinding (propagate C<$@>) only if the catch block was invoked and it raises an exception too. Otherwise, execute the next statement according to local flow-control, because either no exception has been raised by either block, or one was raised in try but it was cleanly caught. =back try { ... } finally { ... } =over 4 =item Z<> Invoke the finally block whether or not the try block raises an exception. Continue unwinding (propagate C<$@>) if either the try block or the finally block raised an exception. Otherwise, execute the next statement according to local flow control, because no exception has been raised. =back try { ... } catch Exception::Foo => { ... } catch Exception::Bar => { ... } catch { ... } # everything else finally { ... } =over 4 =item Z<> In this case if the try raises an exception then the first matching catch is invoked, and then whether or not the try (or any catch) raised any exception, the finally is invoked. Once a catch clause matches, subsequent catch clauses are skipped (like elsif/elsif/else) up to the next finally clause. This try statement unwinds only if try raises an exception and the exception's matching catch block raises an exception, or finally raises an exception. Otherwise no-one raised an exception, or it was in the try and it was cleanly caught. This means that all exceptions propagate unless they are cleanly caught, just as in Perl 5. To prevent this, use: try { fragile(); } catch { } # Go on no matter what. More complicated constructs should be avoided unless the rules in L make sense to you and your target audience. =back =head2 Walkthrough throw Exception::IO "a message", tag => "ABC.1234", ... ; =over 4 =item Z<> Throw is both a class method and an instance method of the built-in Exception class. The indirect object syntax is used to make the throw imperative. As a class method, it is syntactic sugar for: die Exception::IO->new( message => "a message", tag => "ABC.1234", ...); As an instance method it is syntactic sugar for copying over any values given as arguments, and then effecting S>. This allows S> to be used to re-raise exceptions. Note that a derived class can override its constructor to preprocess the optional arguments, so that (for example) tags are parsed out of the message, which allows something like this to work for developers who prefer it (such as the author): throw MyError "ABC.1234: A message."; This also illustrates why the message is a required argument to the throw method. It should not have to be more complicated than that to raise an exception of a given type with a given annotation, in common use. One should not have to always add S>> just for that. If C is followed by a , rather than a class, like this: throw "A Message.", ...; it is the equivalent of: throw Exception "A Message.", ...; =back try { ... } catch => { ... } finally { ... } =over 4 =item Z<> A try statement starts with a block and is followed by zero or more catch and/or finally clauses. The argument of the catch clause is optional, and is described below. C, C, and C blocks should share the same lexical scope, in the way that C and C do. This is so that variables defined in the C block can be operated on in the other blocks, which allows one to say: try { my $fh = open $file } finally { $fh and close $fh } Note that C is a keyword, not a function. This is so that a C<;> is not needed at the end of the last block. This is because a try/catch/finally now looks more like an if/elsif/else, which does not require such a C<;>, than like an eval, which does). =back catch { ... } =over 4 =item Z<> Traps all exceptions, according to the unwind semantics described below. It is a syntax error for a catch all clause like this to be immediately followed by another catch clause, because that would be dead code that could never be executed. Otherwise, it is syntactic sugar for: catch 1 => { ... } To signal failure in a catch block, throw an exception. =back catch Exception::DB => { ... } =over 4 =item Z<> When catch is followed by a class name, the catch block is invoked only if the current error is an instance of said class. It is syntactic sugar for: catch $@->isa($string) => { ... } =back catch Exception::DB, Exception::IO => { ... } =over 4 =item Z<> When catch is followed by a comma-separated list of class names, the catch block is invoked only if the current is an instance of one of the given classes. It is syntactic sugar for: catch grep { $@->isa($_) } @list => { ... } =back catch => { ... } =over 4 =item Z<> Traps exceptions for which the expression returns true, when evaluated at the time the catch clause is attempted, according to the unwinding semantics described below. If multiple statements are required to compute the value of the , use this form: S { ... }>> In order to prevent developers from accidentally writing S { ... }>> (which would be seen as a test that is always true, while they almost certainly were looking for a class name), the compiler should detect that special literal case and issue a compilation warning. =back finally { ... } =over 4 =item Z<> Once the try block is entered, every finally block is guaranteed to be entered before the try statement completes, whether or not any exceptions have been raised since the try block was entered. =back die =over 4 =item Z<> If passed a single argument that isa C, raise it as the new exception and dies in the fashion that Perl 5 does. Otherwise, the arguments are stringified and joined with C<''> (as in Perl 5), the resulting string is wrapped up in a new Exception object (setting the C instance variable to said string), the original unstringified arguments are saved in a list ref in the object's C instance variable, and the new C object is raised. The above guarantees that C<$@ isa Exception> (so developers can depend on that when writing short conditional catch expressions), but because of Exception object stringification, in simple scripts one can still write: open F, $file or die "Can't open $file"; When an exception C<$e> is raised, the equivalent of the following is automatically done, using the variables described below: unshift @@, $@ = $e; This provides the mechanism we use to keep track of raised exceptions while unwinding. =back $@ and @@ =over 4 =item Z<> C<$@> contains the current exception, and C<@@> contains the current exception stack, as defined above under C. The C rule guarantees that C<$@ == $@[0]>. C<$@> and C<@@> can I be set by die, which guarantees that C<$@> and C<$@[$i]> are instances of Exception, as described above. Unlike eval, try does not start by clearing C<$@>. The successful completion of a catch clause clears C<$@> and C<@@>. Tracking C<@@> information is going to become more important if Perl starts using exceptions for error handling. For example, if an C throws and then a calling DB module throws, and then your UI catches it, you are (in many cases) going to want to know why the open threw. =back eval =over 4 =item Z<> Unchanged from Perl 5. =back exception 'Exception::Foo::Bar'; =over 4 =item Z<> Makes C into a class that inherits from the built-in C class, something like: @Exception::Foo::Bar::ISA = 'Exception::Foo'; =back exception 'MyError::App::DB::Foo'; =over 4 =item Z<> Makes C into a class that inherits from the built-in C class. If the given name matches C, something like this happens: @MyError::App::DB::Foo::ISA = 'MyError::App::DB'; and all non-existent parent classes are automatically created as inheriting from their parent, or Exception in the tail case. If a parent class is found to exist and not inherit from Exception, a run-time error exception is raised. If the given name does not match C (say it's just C), this happens instead: @Alarm::ISA = 'Exception'; This means that every exception class isa Exception, even if C is not used at the beginning of the class name. The exception function can also take optional arguments, along the lines of exception 'Error_DB', isa => "MyError::App"; which results (after error checking) in something like @Error_DB::ISA = 'MyError::App'; Other options may possibly be given to C to control things like the raise-time stack traceback. =back =head2 Examples The first three of these examples cover the most common uses of try, catch, and finally. try { my $f = open "foo"; ... } finally { $f and close $f; } =over 4 =item Z<> If the open completed successfully (so defining C<$f>), C is attempted no matter what else happens. If anything in the try or finally clauses raises an exception, said exception is propagated after attempting the finally block. Otherwise execution continues locally. This sort of functionality is commonly required, not just with files and close, but (for example) with objects that don't want to rely on garbage collection for the invocation of cleanup code. =back try { fragile(); } catch { print "$@\n"; } =over 4 =item Z<> If the try block raises an exception, the catch block is entered. No exception is propagated, unless the catch block raises an exception (because if it doesn't the exception raised by try is considered "cleanly caught"). In this example, if C raises an exception it's shown on C, and then execution continues normally (unless C raises an exception). The stringification method of the exception object is used to generate the output. Note that Sshow;>> can be used to dump the entire exception unwind stack, as described elsewhere herein. =back try { ... } catch Exception::Foo => { ... } finally { ... } =over 4 =item Z<> If the try block raises an C exception then the catch block is invoked. Whether or not try or catch raised an exception, the finally block is invoked. If try and catch both raised exceptions, or finally raised an exception, then it is propagated, but if try raised an exception and catch and finally didn't, then no exception is propagated (because the exception was cleanly caught). =back Here's another simple yet quite robust example based on the first two examples. sub attempt_closure_after_successful_candidate_file_open { my ($closure, @fileList) = @_; local (*F); foreach my $file (@fileList) { try { open F, $file; } catch { next; } try { &$closure(*F); } finally { close F; } return; } throw Exception "Can't open any file.", debug => @fileList . " tried."; } Most developers will usually only find need for cases like those shown above. The following examples can be used when circumstances merit, but they should be avoided by those looking for maximal simplicity. Anything can be done with state variables and nested simple trys, at least until you run off the right of the page or forget a re-throw. The following examples are provided for your convenience, not as a requirement to understanding this RFC or using its mechanism. try { ... } catch Exception::Foo => { ... } catch Exception::Bar => { ... } catch { ... } finally { ... } =over 4 =item Z<> If the try block raises an exception then: if the first catch matches it is invoked, if the second catch matches it is invoked, otherwise the third catch is invoked. The finally block is entered no matter what happens. No exception is propagated unless one of the catch blocks raises an exception, or the finally block does (because otherwise any exception raised by try is considered to have been "cleanly caught"). =back try { ... } catch $@->{message} =~ /.../ => { ... } =over 4 =item Z<> Any exception object instance variable, such as message, tag, or severity, can be used to test whether or not the exception should be caught. The result of the stringification of C<$@> can be similarly tested. This also allows one to define a convenience subroutine to make, for example, the following work: catch not &TooSevere => { ... } =back try { ... } catch ref $@ =~ /.../ => { ... } =over 4 =item Z<> Catches the current exception if has a class name that matches the given regular expression! =back try { ... } catch grep { $_->isa("Foo") } @@ => { ... } =over 4 =item Z<> The catch clause is invoked only if any exception on the exception unwind stack, aka C<@@>, isa Foo. Note that developers of applications that need to search the stack a lot can define a simple convenience subroutine to allow catch AnyException("Error::IO") => { ... } which is an excellent example of the utility of the SexprE>> form. =back try { ... } catch grep { $@->isa($_) } @list => { ... } =over 4 =item Z<> Rather that repeating a long list of exceptions that you need to catch in multiple places, this form allows you to put them in a list. Again, in practice, a convenience subroutine would probably be defined to hold the list and compute the predicate. =back try { ... } catch $@->isa("Foo") && $@->CanBar => { ... } =over 4 =item Z<> Derived exception classes can add new instance variables and methods to do things like test new predicates. The above form allows these predicates to be used with try. If polymorphism is desired, use catch $@->can("CanBar") && $@->CanBar => { ... } =back try { my $p = P->new; my $q = Q->new; ... } finally { $p and $p->Done; } finally { $q and $q->Done; } =over 4 =item Z<> This construct makes sure that if C<$q> is successfully constructed, then C<$q-EDone> is invoked even if C<$p-EDone> raises an exception. Works for opening and closing a pair of files too. =back try { TryToFoo; } catch { TryToHandle; } finally { TryToCleanUp; } catch { throw Exception "Can't cleanly Foo."; } =over 4 =item Z<> Unshifts a new exception onto C<@@> if any of the first three blocks throws, unless successfully handled. Use of this technique at major API entry points can result in the availability of better information when unwinding is eventually caught, 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 Locations table. IOM.5678: Can't open file ".../locations.ndx". IOM.6789: File ".../locations.ndx" not found. The following table shows the details of this construct. The key is S<1 = succeeds>, S<0 = fails>, S. TryTo TryTo TryTo | Throw After Finally Foo Handle CleanUp | Description ------------------------+-------------------------------------- 1 x 1 | No. We did Foo and cleaned up. 1 x 0 | Yes. We did Foo but didn't clean up. 0 1 1 | No. We didn't Foo but did Handle it, | and then we cleaned up. 0 1 0 | Yes. We didn't Foo but did Handle it, | but then we didn't clean up. 0 0 1 | Yes. We didn't Foo and didn't Handle | it, even though we did clean up. 0 0 0 | Yes. We didn't do anything right! =back try { ... $avoidCatches_JustUnwind = predicate(); ... } catch $avoidCatches_JustUnwind => { throw $@ } catch { ... } finally { ... } =over 4 =item Z<> This construction allows a try to dynamically decide to avoid its catch clauses and just propagate any error that occurs in the try block (after invoking the finally block). =back =head2 Syntax := exception ; := throw ; | throw ; | throw ; := # empty | | := try := # empty | := | := catch | catch | catch := finally := | , # Unquoted name of a class that inherits from the # built-in Exception class, such as Exception::Foo. := # empty | , => | , => := { ... } # A Perl code block. := , | => # A Perl expression which is evaluated when # the relevant clause is processed. # A Perl expression which is evaluated and # stringified. =head2 Unwinding Semantics "Computer Architecture, Hardware and Software" [RYK-1989] says (p.347): =over 4 =item Z<> Three basic design issues concerning exceptions are (1) whether or not the exception is permitted to return to the point where the exception is taken, (2) how the execution context to be used during the the handler's execution is found, and (3) how the association of an exception handler with and exception event is established. =back Perl's behaviour after a C starts call-stack unwinding, as envisioned by this RFC, is as described by the following rules. The "current exception" is the value of C<$@>. =over 4 =item 1 Whenever an exception is raised Perl looks for the first enclosing try/catch/finally block, or eval. If none is found program shutdown is initiated. If a try/catch/finally clause is found Perl traps the exception and proceeds as per rule 2. If the exception is trapped by an eval the eval returns locally, preserving the value of C<$@>, according to the usual Perl 5 rules (regardless of any outer try/catch/finally clauses). Z<> =item 2 The try block's "next" associated trap/catch or finally clause is processed according to rules 3 and 4. When there are no more clauses rule 5 is used. Z<> =item 3 If a catch returns true (without itself raising an exception), its associated catch block is entered. If the catch block is entered and it completes without itself raising an exception, the current exception and stack are cleared. But if a catch or a block raises an exception, it becomes the current exception, but it does not propagate out of the try statement (at this point). If a catch raises an exception or returns true, then whether or not the catch block raises an exception, any succeeding try/catch clauses up to the next finally clause are skipped (for the purpose of the "next" iterator in rule 2). This makes sequential catches work like switch/case. Processing then continues with rule 2. Z<> =item 4 When a finally clause is encountered its block is entered. If the finally block raises an exception it becomes the current exception, but it does not propagate out of the statement (at this point). Processing continues with rule 2. Z<> =item 5 After the catch and finally blocks are processed, if there is a current exception then it is re-raised and propagated as per Rule 1 (beginning above the current try statement in the call stack). Otherwise the current exception and stack are cleared, the try statement completes normally, and Perl continues with the statement after the try statement. =back =head2 Built-In Exception Class Exceptions raised by the guts of Perl are envisioned by this RFC to all be instances of derived classes that inherit from Exception (so the class hierarchy can be used for exception classification). This is discussed below under L + RFC 80. The built-in Exception class and unwinding functionality described in this RFC can be used whether or not Perl 6 goes to internal exceptions. Instances of the actual (non-subclassed) Exception class itself are used for simple exceptions, for those cases in which one more or less just wants to say S>, without a lot of extra tokens, and without getting into higher levels of the taxonomy of exceptions. =head2 Instance Variables The built-in Exception class reserves all instance variable and method names matching C. The following instance variables are defined. B> =over 4 =item Z<> This is a description of the exception in language intended for the "end user". Potentially sensitive information should not be included here. This instance variable is also used to record the string given when S> is used. Because of stringification (described below), such a C will result in "$@" =~ /Can't foo\./ =back B> =over 4 =item Z<> This is a string which package developers can use to assign a unique "identifier" to each exception object constructor invocation in their package. A package-based namespace control mechanism that helps ensure the uniqueness of tags is described below, under the C and C methods. The tag instance variable always contains the fully qualified tag value. =back B> =over 4 =item Z<> This is a place for additional description that is not intended for the end user (because it is "too technical" or "sensitive"). For example, in a web application, internal file names might be considered sensitive information, but you would still like to get the name of the file that couldn't be opened into the server log. =back B> =over 4 =item Z<> If the exception is related to some particular object, or the developer wants to associate other information with this exception, said $data can be specified via: throw Exception "...", data => $data; =back B> =over 4 =item Z<> This is a listref containing a snapshot of the Perl call-stack as at the time the exception is first raised. The array contains hashes (one per call stack level), each containing one key-value pair for each snapshot value at that level. Here are some examples: $e->{trace}->[0]->{file} $e->{trace}->[0]->{line} $e->{trace}->[0]->{package} Alternatively, C<$e-E{trace}> could be some sort of snapshot object thingy. Similar stuff has been done by the Perl 5 C bundle; perhaps there should be a Perl 6 RFC for it. The functionality of C is used to populate this snapshot data, but said snapshot has to be taken early and held for possible historic debugging use, because usually we want to know where we were when things went wrong, I where we were when we caught that (by which time, the raise-time stack snapshot data is irrecoverable). This snapshot data is set up by the C method described below, so that derived classes that don't want this overhead can override that method to do nothing. This can be important to applications that want to use a large number of light-weight exceptions to implement non-local success-based flow-control gotos, where the cost of taking and maintaining the snapshots could prove to be prohibitive, especially since they would normally never be used. The snapshot method is an overrideable built-in rather than a stub though, because in fact in most cases one does want to pay the price for being able to debug exceptions, because said price is small (in most cases). =back B> =over 4 =item Z<> This a place for the internal exceptions raised by Perl to record system information, along the lines of C<$!>, C<$?>, and C<$^E>. Note that the sysmsg instance variable is properly the province of the various "exceptions for built-ins" proposals, such as RFC 80. However, if the C<%@> concept described under L becomes a part of Perl 6, then C could just be a reference to a copy of that hash. =back =head2 Methods The built-in Exception base class defines the following methods. B> =over 4 =item Z<> Constructs a new object instance, using its arguments as a hash to initialize the instance variables by name. The C instance variable is treated specially in order to control the namespace for tags, as follows: $self->{tag} = $self->settag($arg{tag}); =back B> =over 4 =item Z<> As a class method a new instance is constructed, passing any arguments to the constructor. This object becomes C<$self>, and we now have an instance method. As an instance method, any arguments are used to update the instance variables (unless just constructed), and this method effects S>. This method does not return to local flow control (modulo the C mechanism described below). =back B> =over 4 =item Z<> Stringification produces a concatenation of various Exception object instance variables and certain delimiters. The message instance variable is always to be included. The details are to be worked out, but an example would be: ABC.1234 [Warning]: Confirmed but not acknowledged. =back B> =over 4 =item Z<> This method effects whatever operations are performed on the tag short form given in a throw like this throw Exception "a message", tag => "My/Tag/Scheme" in order to come up with fully qualified value to be stored in the tag instance variable. These operations are properly the province of RFC 80, but the default could be something like this: $self->{tag} = (ref $self) .".". $_[0]; or equivalent, as described below. This would mean that the tags provided to exception constructors need only be unique across a class, file, or package, which is often constrained enough to make uniqueness relatively easy in practice. The example of exception class constructor overriding given later in this RFC needs to set the tag, while maintaining namespace control, without knowing the namespace control details, so this method is provided for that. Perhaps all this instance variable accessor method stuff can be cleaned up with other changes to the Perl 6 OO mechanism. =back B> =over 4 =item Z<> This method returns true if the object's tag instance variable matches its argument. This allows us to easily trap by namespace-controlled tag, using a form like this: catch $@->tag("Foo") => { ... } The definition of "matches" can be changed, for an exception class hierarchy, by overriding this method. The default rule properly is the province of RFC 80, but it could be something like this (for unique tags across all throws of the same class): $self->{tag} eq (ref $self) .".". $_[0] or for unique tags across all throws in the same package: $self->{tag} eq (caller(0))[0] ."\t".$_[0] or for unique tags across all throws in the same file: $self->{tag} eq (caller(0))[1] ."\t".$_[0] =back B> =over 4 =item Z<> This class method and instance method generates a string formatted version of the exception unwinding stack based on the contents of C<@@>, stringifying each exception object as it goes. For example, Sshow>> produces something like this: UIM.1234: Can't add a new person to database. APP.2345: Can't update Company relationship. DBM.4567: Unable to write to Company table. IOM.5678: Can't open file ".../company.db". C takes the following optional parameters. C