=head1 TITLE
Structured Exception/Error Handling Mechanism
=head1 VERSION
Maintainer: Tony Olekshy <[EMAIL PROTECTED]>
Date: 19 Aug 2000
Version: 2 (Draft 3)
Mailing List: [EMAIL PROTECTED]
Number: 88
=head1 DRAFT STATUS
This redaction has been modified to reflect Peter Scott's comments
through 2000-08-18.
Areas of development of this document which are not yet complete as
of this draft are annotated with the --> glyph. This version is not
intended to be the final redaction of this RFC, as there remains
opportunity to enhance the focus and clarity of this document to
the benefit of the reviewers hereto.
The inclusion of a definitions section for terms like propagate,
unwind, exception, and error is under consideration.
This RFC needs to better explain the purpose behind factoring Error
from Exception, an to more clearly explain the matter of RFC 80.
Production editing and spell checking have not been done yet.
=head1 ABSTRACT
"The Encyclopedia of Software Engineering" [ESE-1994] says (p.847):
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
excecution may result in a failure. [...] The programming
laguage 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.
This RFC describes a collection of changes and additions to Perl,
which together support built-in base classes for Exception and
Error objects, and exception and error handling code like this:
exception 'Error::DB';
try {
throw Error::DB "a message", tag => "ABC.1234", ... ;
}
catch Error::DB { ... }
catch Error::DB, Error:IO { ... }
catch $@->{message} =~ /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 Error 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 and Error base classes whether or not
that happens.
Readers who are not familiar with the technique of using exception
handling to handle errors should refer to the CONVERSION section
of this document first.
=head1 DESCRIPTION
exception 'Error::App::DB::Foo';
Makes Error::App::DB::Foo into a class that inherits from the
built-in Exception class.
If the given name matches /::/, something like this happens:
@Error::App::DB::Foo::ISA = 'Error::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 /::/ (say it's just 'Success'),
this happens instead:
@Success::ISA = 'Exception';
This means that every exception class isa Exception, even if
Exception:: 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 => "Error::App";
which results in something like
@Error_DB::ISA = 'Error::App';
Other options may possibly be given to C<exception> to control
things like the raise-time stack traceback.
throw Error::DB "a message", tag => "ABC.1234", ... ;
Throw is both a class and an instance method of the build-in
Exception class. The indirect object syntax is used to make the
throw imperitive. As a class method, it is syntactic sugar for:
die Error::DB->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 C<die $self>.
This allows C<throw $@> 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
"message =>" just for that.
try { ... } catch EXPR { ... } finally { ... }
A try statement starts with a block and is followed by zero
or more catch and/or finally clauses.
The expression argument of the catch clause is optional, and
is described below.
C<try>, C<catch>, and C<finally> blocks should share the same
lexical scope, in the way that C<while> and C<continue> do.
This is so that variables defined in the C<try> 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<try> 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).
catch { ... }
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 { ... }.
catch Error::DB { ... }
When catch is follwed 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) { ... }
catch Error::DB, Error:IO { ... }
When catch is follwed by a comma-seperated 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 { ... }
catch EXPR { ... }
Traps exceptions for which the EXPR returns true, when evaluated
at the time the catch clause is attempted, according to the
unwinding sematics described below.
If multiple statements are required to compute the value of the
EXPR, use this form: catch do { a; b } { ... }
finally { ... }
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.
die
If argument isa "Exception", raise it as the new exception and
die in the fashion that Perl 5 does.
If argument is a string, wrap it in a new Error object, setting
the message ivar to the given string, and raise that instead.
If argument is anything else, raise a run-time exception.
The above guarantees that exceptions are Exceptions (because
this mechanism depends on that), 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 $e is raised, 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.
$@ and @@
$@ contains the current exception, and @@ contains the current
exception stack, as defined above under C<die>.
$@ and @@ can I<only> be set by die, which guarantees that
$@ and $@[$i] are instances of Exception, as described above.
Unlike eval, $@ is not cleared when a try statement starts.
However, eval should continue to clear $@ and should in Perl 6
clear @@, even though they are otherwise read-only.
Tracking @@ information is going to become more important if
Perl starts using exceptions for error handling. For example,
if an open 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.
eval
Eval should now clear @@ as well as $@.
=head2 Examples
The first three of these examples cover the most common uses of
try, catch, and finally.
open(*F, ">foo"); try { ... } finally { close F; }
If the open raises an exception, it is propagated (for some
outer try to handle), and the try statement is skipped.
If foo was opened, try { ... } is attempted, and then F is
closed whether or not the try block raised an exception.
Then, if try or close raised an exception, it is propagated
for some outer try to handle.
This guarantees that no matter what else happens, if the
file was opened it is closed. 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.
try { fragile(); } catch { print "$@\n"; }
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 fragile() raises an exception it's
shown on STDOUT. The stringification method of the
exception object is used to generate the output. Note
that C<print $@->show;> can be used to dump the entire
exception unwind stack, as described elsewhere herein.
try { ... }
catch Error::IO { ... }
finally { ... }
If the try block raises an Error::IO 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).
Here's another simple yet quite robust example based on the first
two examples.
sub AttemptClosureAfterSuccessfulCandidateFileOpen
{
my ($closure, @fileList) = @_; local (*F);
foreach my $file (@fileList) {
try { open F, $file; } catch { next; }
try { &$closure(*F); } finally { close F; }
return;
}
throw Error "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 from maximal
simplicity. Anything can be done with state variables and nested
simple trys, at least until you run off the right of the page. The
following examples are provided for your convenience, not as a
requirement to understanding this RFC or to using its mechanism.
try { ... }
catch Error::IO { ... }
catch Error::DB { ... }
catch { ... }
finally { ... }
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").
try { ... } catch $@->{message} =~ /.../ { ... }
Any exception object instance variable can be used
to test whether or not the exeption should be caught.
try { ... } catch ref $@ =~ /.../ { ... }
Catches the current exception if has a class name that
matches the given regular expression!
try { ... } catch grep { $_->isa("Foo") } @@ { ... }
The catch clause is invoked only if any exception on
the exception unwind stack, aka @@, isa Foo.
try { ... } catch $@->isa("Foo") && $@->CanBar { ... }
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 { ... }
my ($p, $q);
try { $p = P->new; $q = Q->new; ... }
finally { $p and $p->Done; }
finally { $q and $q->Done; }
This construct makes sure that if $q is successfully
constructed, then $q->Done is invoked even if $p->Done
raises an exception.
try { TryToFoo; }
catch { TryToHandle; }
finally { TryToCleanUp; }
catch { throw Error "Can't cleanly Foo."; }
Unshifts a new exception onto @@ 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 Company table.
IOM.5678: Can't open file ".../company.db".
IOM.6789: Access to ".../company.db" denied.
The following table shows the details of this construct.
The key is 1 = succeeds, 0 = fails, x = skipped.
TryTo TryTo TryTo | Post Finally Throw
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!
try {
$avoidCatches_JustUnwind = predicate();
}
catch $avoidCatches_JustUnwind { throw }
catch { ... }
finally { ... }
This construction allows a try to dynamically decide
to avoid its catch clause and just propagate any
error that occurrs in the try block (after invoking
the finally block).
=head2 Syntax
<exception> := exception <string> , <options>? ;
<throw> := throw <X> <string>? , <options>? ;
<try> := try <block> <clause>* ;
<clause> := <catch> | <finally>
<catch> := catch <test> <block>
<finally> := finally <block>
<test> := | <classes> | <expr>
<classes> := <class> | <class> , <classes>
<X> := <class> | <object>
<class> # Unquoted name of a class that inherits from the
# built-in Exception class, such as Error::IO.
<object> # An instance of the built-in Exception class.
<options> := name => $value
| name => $value, <options>
<block> := { ... } # A Perl code block.
<expr> # A Perl expression which is evaluated when
# the relevent clause is processed.
<string> # A Perl expression which is evaluated and
# stringified.
=head2 Unwinding Semantics
"Computer Architecure, Hardware and Software" [RYK-1989] says (p.347):
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.
Perl's behaviour after a C<die> starts call-stack unwinding, as
envisioned by this RFC, is as described by the following rules.
1. Whenever an exception is raised Perl looks for an enclosing
try/catch/finally clause.
If such a clause is found Perl traps the exception and proceeds
as per rule 2, otherwise program shutdown is initiated.
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.
3. If a catch <expr> 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 <expr> or a block clause raises an
exception, it becomes the current exception, but it does
not propagate out of the clause (at this point).
If a catch <expr> 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).
Processing then continues with rule 2.
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 clause (at this
point).
Processing continues with rule 2.
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 $@ is undef, the try statement completes normally,
and Perl continues with the statement after the try statement.
=head2 Built-In Exception & Error Classes
In addition to the built-in Exception class described below (which
inherits from UNIVERSAL), a built-in Error class is also defined,
which inherits from Exception.
Exceptions raised by the guts of Perl are envisoned by this RFC to
all be instances of classes that inherit from Error. Instances of
the actual Error class itself are reserved for simple exceptions,
for those cases in which one more or less just wants to say C<throw
Error "My message.">, without a lot of extra tokens, and without
getting into higher levels of the taxonomy of exceptions.
On the other hand, new exception classes that inherit directly from
Exception, as opposed to from Error, are assumed to be asking for
more light-weight functionality. The intent of this RFC is to
provide a place (Exception) in which methods can be stubbed in for
the functionality required by Errors, so that when they are
overridden by Error they work as expected, but when inherited by
other derivatives of Exception, this error-functionality is avoided
and does not otherwise interfer with the requirements of light-
weight exception handling protocols. The stack-traceback at throw-
time instance variable, for example, probably doesn't make much
sense when one is throwing success, not failure.
This Exception/Error class factoring is taken advantage of in
this RFC to delegate the details of the Error class to proposals
such as RFC 80 et al, independent of the fact that this RFC expects
such an Error class to inherit from Exception.
=head3 Instance Variables
The built-in Exception and Error classes reserve all instance
variable and method names matching C</^[_a-z]/>. The following
instance variables are defined.
message
This is a description of the exception in language intended
for the "end user". Potentially sensitive information should
not be included here.
This ivar is also used to record the string given when
C<die "Can't foo."> is used. Because of stringification
(described below), C<$@."" eq "Can't foo."> (for some value
of eq).
tag
This is a string which module developers can use to assign
a unique "identifier" to each exception object constructor
invocation in the module. A namespace control mechanism
that helps ensure the uniqueness of tags is described below.
debug
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.
object
If the exception is related to some particular object, this can
be specified via:
throw Exception "...", object => $object;
This should probably be a weak reference.
trace
A listref containing a snapshot of the 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]->{sub}
Alternatively, $e->{trace} could be some sort of snapshot object
thingy. Similar stuff has been done by the Perl 5 Devel bundle;
perhaps there should be a Perl 6 RFC for it.
The functionality of caller() 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<not> 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 "snapshot" 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).
severity
This is some sort of "priority" (such as info v/s fatal) on
which handing can be based.
Note that a severity ivar is properly the province of the Error
class, which is covered in other proposals such as RFC 80. It
has been included here to illustrate the kind of functionality
envisioned by this RFC.
sysmsg
This a place for the internal exceptions raised by Perl to
record system information, along the lines of $!, $?, and $^E.
The use of this ivar is the province of proposals like RFC 80.
It is provided here as an example of possible functionality.
=head3 Methods
The built-in Exception base class defines the following methods.
new
Constructs a new object instance, using its arguments as
a hash to initialize the instance variables by name.
The "tag" instance variable is treated specially in order
to control the namespace for tags, as follows:
$self->{tag} = $self->settag($arg{tag});
throw
As a class method a new instance is constucted, passing any
arguments to the constructor. This object becomes $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 C<die $self>.
This method does not return to local flow control (modulo the
handler mechanism described below).
handler
Derived classes may override this method to attempt to "handle"
an exception or otherwise manipulate it, just before it is raised.
If handler throws or does not return true the exception is raised,
otherwise it is not.
In Exception, this method returns false.
This makes no sense for Error objects and is abhorent to users
thereof, but that's ok because Exception's C<handler> method
returns false, and Error doesn't override it, and, well, Errors
could use handler for ivar munging as long as they return true.
However, other uses for Exception (but non-Error) objects may
want this functionality, at least based on the reference and
text books which say that classically, an exception has such a
handler hook.
If we do provide it, it need not be used, but if we don't
provide it the functionality cannot otherwise be provided,
should it be deemed desireable for some application.
C<overload '""'> (Stringification)
Stringification produces a concatentation 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.
tag
This method returns true if
$self->{tag} eq (ref $self) .".". $_[0]
This allows us to easily trap by namespace-controlled tag,
using a form like this:
catch $@->tag("Foo") { ... }
settag
This method effects
$self->{tag} = (ref $self) .".". $_[0];
This means that the tags provided to exception construcors
need only be unique across a derived class, which is often
constrained in such a manner as 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 ivar accessor method stuff can be cleaned
up with other changes to the Perl 6 OO mechanism.
snapshot
Used internally to generate the "trace" instance variable.
Designed to be overridden in derived classes for performance
or extension purposes. See the description of the trace
instance variable, above.
show
This method generates a string formatted version of the
exception unwinding stack based on the contents of @@,
stringifying each exception object as it goes. For example,
C<print $@->show> 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<show> takes the following optional parameters.
label => 1
If set, the formatted exception stack is annotated with
the classes of the objects therein, as in:
Error::UI: UIM.1234: Can't add a new person to database.
Error::DB: APP.2345: Can't update Company relationship.
Error::DB: DBM.4567: Unable to write to Company table.
Error::IO: IOM.5678: Can't open file ".../company.db".
trace => 1
If set, the value returned by C<show> includes the Perl
stack traceback using the information from the *last*
exception in the "link" chain (that is, from the point
where unwinding first started). 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".
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:
UIM.1234: Can't add a new person to database.
Debug: Fred Flintstone
APP.2345: Can't update Company relationship.
DBM.4567: Unable to write to Company table.
IOM.5678: Can't open Company file.
Debug: /foo/bar/company.dat
=head3 Custom Exceptions
In addition to the C<exception 'MyException'> mechanism described
above, custom exception and/or error classes can be created along
the following lines:
File main.pl:
use Error::App;
exception 'Error_DB', isa => 'Error::App';
throw Error_DB "ABC.1234: Can't foo.";
File Error/App.pm:
package Error::App; @Error::App::ISA = 'Error';
sub new
{
my ($C, $msg, %opt) = @_; $C = ref $C || $C;
my $self = $C->SUPER::new($msg, %opt);
$self->{message} =~ s/^([A-Z]+\/\d+):\s+//
and
$self->settag($1);
return bless($self, $C);
}
Without this functionality and the other mechanisms described
in the RFC, instead of having a way to be able to write
throw Error_DB "ABC.1234: Can't write to table $table.";
a developer would be I<required> to write something like
throw Exception::Error::App::DB tag => "ABC.1234",
message => "Can't write to table $table.";
The latter has a much lower signal to noise ratio than the
former, which is of significant importance to regular users
of exception handling mechanisms.
Note that the scope of classes like Error::App is is limited to
packages that use it, which presumably want such functionality.
=head1 MOTIVATION
Over the last ten years, the author has come to rely on exception
handling as a relatively robust mechanism for error handling, and
has used exception handling to implement other, non-error related,
non-local flow-control algorithms. He has developed a relatively
complete implementation of the functionality described herein, in
Perl 5, in the Try.pm module and its associated regression tests
[TRY-2000]. Try.pm in used in large production applications.
The author's motivation is to help Perl 6 achieve a relatively
robust exception handling mechanism that is suitable for error
handling via exceptions, is still capable enough to handle the
needs of production programs, and is still suitable for light-
weight exceptions that may not involve errors at all.
"The Encyclopedia of Software Engineering" [ESE-1994] says (p.847):
Among the features offered by programming languages to
support exception handling are the following.
1. The ability to distinguish the normal control flow from
the exception handling flow to make the structure of the
program clear.
2. The ability to detect faults as they occur in the program,
not only by explicitly raising an exception but also by
implicitly raising it on account of the run-time environment.
[...] Both kinds of faults should be handled uniformly.
3. The ability to transfer control to a programmer-definable
exception handler when the fault is detected. The language
should specify the rules by which this a detected fault is
bound to its corresponding exception-handling routine.
4. The ability to specify how control flows after the exception
handler is executed, i.e., whether one can resume execution
from the point at which it left off, or whether the program
should fail.
Most early programming languages do not provide specific
features for exception handling, but rather use the normal
constructs to implement it. [...] Obviously this and other
ad hoc methods to not satisfy the requirements listed above.
To this end, new keywords have been deliberately chosen to represent
the new mechanism, in order to make it clear to the developer when
the code is expecting to deal with unwind semantics (rather than
with local flow control).
In addition, the exception handling mechanism propagates exceptions
that are not cleanly caught, which minimizes the chances for the
developer to forget to re-raise uncaught exceptions. How many of
us check for IO failures after C<print>s? And if you're writing a
simple program you wouldn't want to, but you would want the program
to shut down after a failure even if you don't check.
Remembering to always check all subroutine and functions for failure
return codes can be difficult, since nothing about the form of the
call, in the source code, indicates whether or not an possible
failure return code is excpected. And, the exception handling
technique not only works with subroutines and functions, it also
works with operators and with syntax errors.
Although the following code using the new mechanism:
try { may_throw_1 }
catch may_throw_2 { may_throw_3 }
finally { may_throw_4 }
can be written in Perl 5 like this:
eval { may_throw_1 };
my $unwinding = $@;
if ($unwinding) {
my $test = eval { may_throw_2 };
$@ and $unwinding = $@;
if ( ! $@ and $test ) {
eval { may_throw_3 };
$unwinding = $@;
}
}
eval { may_throw_4 };
($unwinding ||= $@) and die $unwinding;
the opportunity for flow-control errors increases.
=head1 CONVERSION
Although the technique of using exception handing for error
handling often seems foreign at first to developers who are not
used to it, many find that it becomes quite natural when four
concepts are kept in mind.
1. Wherever you previously would have C<return undef> or some
other special return code (or a pass-by-reference value),
to indicate the failure of a subroutine or function, instead
use C<throw Error> (or some fancier form thereof).
2. Wherever you previously would have written
$x = foo(); defined $x or return undef;
to propagate an error from a callee back to your caller, you
can just write C<$x = foo();> because unhandled exceptions
automatically propagate.
3. Wherever you previously would have written the equivalent of the
following to do something about an error and then propagate it
$x = foo();
unless (defined $x) {
# do something about error
return undef;
}
you can now write
try { $x = foo(); }
catch {
# do something about error
}
4. Wherever you previously would have ignored an error in order
to allow you to restore invariants or enforce postconditions,
and then C<return undef> to propagate the error, like this:
open F, ...;
$x = foo();
close F;
defined $x or return undef;
you can now write
open F, ...;
try { $x = foo(); }
finally { close F; }
because unhandled exceptions automatically propagate.
=head1 ISSUES
Object Model
This RFC is written using the basic Perl 5 concept of an object
as a reference to a blessed hash containing instance variable
name-value pairs. It may need to be modified to account for
any new basic Perl 6 object model.
Syntax
If C<catch EXPR { ... }> proves to be difficult for Perl to
parse, even in the form C<catch EXPR => { ... }> then the form
C<trap { EXPR } catch { ... }> is a possible alternative (based
on earlier versions of this RFC and mailing list discussions
thereto).
Lexical Scope
If it is not possible to have try, catch, and finally blocks
share lexical scope (due, perhaps, to the vagueries of stack
unwinding), this feature can simply be deleted, and the outer
scope can be shared.
This matter will have to be referrred to the good folks over
in internals.
Keyword Names
RFC 88 only introduces the try, throw, trap, catch, finally,
and exception keywords, which are all traditionally related
to exception handling. And they don't even need to all be
keywords, some can be functions (depending on the Perl 6
parser).
The "throw" verb was chosen for raising an exception, because
of its neutral connotation (unlike "fail" for example), because
exceptions do not necessarily encapsulate a negative.
New kewords were chosen so that this can be written:
try {
try {
try { ... }
finally { ... }
}
catch Error1 { ... }
catch Error2 { ... }
catch { ... }
}
catch { print $@, "\n"; }
instead of cognitively overloading existing keywords and
concepts in a manner like this:
eval {
eval {
eval { ... }
continue { ... }
}
else {
switch ($@) {
case /First/ { ... }
case /Second/ { ... }
else { ... }
}
}
}
else { print $@, "\n"; }
because the author is of the opinion that overloading else and
continue with unwind semantics not traditionally associated with
else and continue can be confusing, especially when intermixed
with local flow-control forms of else and continue (which may
be present in any { ... } block), or when an "else die $@" is
forgotten on a switch that needs to re-throw.
Some perl6-language-error discussions have suggested leaving out
the try altogether, as in simply writing C<{ } else { }>. Yikes!
The "try" is not for Perl's sake. It's for the developers's
sake. It says, watch out, some sort of non-local flow control
is going on here. It signals intent to deal with action at a
distance (unwinding semantics). It satisfys the first rule
listed under MOTIVATION.
retry
There has been some discussion on perl6-language-error about
the concept of re-entering try blocks on catch, and the
possibility of using such a mechanism to replace AUTOLOAD.
The author is of the opinion that in order to do this sort
of thing properly one should use continuations, which are
being discussed elsewhere to this RFC.
The intent of this RFC is to provide a simple yet robust
exception handling mechanism that is suitable for error
handling, not for replacing AUTOLOAD.
eval
The semantics of eval are, "don't unwind unless the user re-dies
after the eval". The semantics of try are "unwind after try
unless any raised exception was cleanly and completely handled".
In the author's opinion, both eval and try should exist in Perl
6. This would also mean that the legacy of examples of how to
use eval in Perl will still work.
And, of course, we still need C<eval $string>.
However, discussions on perl6-language-errors have shown that
some would prefer the eval { ... } form to be removed from Perl
6, because having two methods of exception handling in Perl
which could be confusing to developers. This would in fact be
possible, since the same effect can be achieved with:
my $e;
try { ... } catch { $e = $@ }
# now process $e instead of $@
However, it would be necessary to provide some way to clear
the otherwise read-only $@ and @@ variables, should that
functionality be deemed desireable.
try v/s eval
Some participants in discussions on perl6-language-errors have
expressed the opinion that not only should C<eval> be used
instead of C<try>, but C<else> should be used instead of
multiple C<catch> blocks. One advantage of their approach is
that you can use an expression for the exception you wish to
catch in a C<switch> block:
eval { ... }
else {
switch ($@) {
case ^_->isa('Error::IO') { ... }
case ^_->my_method { ... }
}
}
In this case, the problem arises, how should the code implicitly
rethrow uncaught exceptions? Without having to look inside the
C<switch> statement to see what it did? Most of the proponents
of this model think that uncaught exceptions should not be
implicitly rethrown; one suggests that the programmer should
C<undef $@> at the end of every successful <case> block, so that
perl rethrows any C<$@> still extant at the end of the C<else>.
This RFC allows a switch to be used in a catch { ... } clause,
for cases where that approach would minimize redundant code in
catch <expr> { ... } clauses, but with the mechanism proposed in
this RFC, the switch functionality shown above can be written
like this, while still maintaining the automatic exception
propagation when no cases match, and while saving 4 lines and
two levels of indenting:
try { ... }
case Exception::IO { ... }
case $@->my_method { ... }
From a minimalist point of view, C<else> is an plausible suggestion
that should be considered if this RFC is evaluated as requiring
too many new keywords. Our objection to it is that it requires
the programmer to insert C<undef $@> in every instance of by far
the most common type of clause, which tends to be an extreemly
risky error-prone technique.
Exception Base Class
--> Some of these now belong in Error, and therefore RFC 80 et al.
The following questions about Exception should be addressed.
How to extend ivars and control namespace?
How to extend methods and control namespace?
Default values for tag and severity?
How to categorize severity?
How to arrange the exception class hierarchy for the Perl core?
How to tag exceptions in the Perl core?
What assertions should be placed on the instance variables?
What should stringification return?
What can be delegated to Error for performance?
Exception/Error Factorization
The author is of the opinion that these classes should be
factored as an architectural matter. However, they could
be merged, resulting in some simplification of this RFC.
One advantage of leaving them factored is that changes to the
Error class, over the life of Perl 6, will not, a prior, affect
the use of Exception by those who need light-weight exception
objects for their algorithm.
Handler
Some participants in discussions on perl6-language-errors
currently prefer that the functionality represented by
the handler method of the Exception class not be include at
all. Should that be deemed to be the case, the C<handler>
method of the Exception class can be left out.
It has also been mentioned that we should consider making
overriding of throw impossible, so that throw must die.
Independent of the matter of throw and handler methods, there
should be I<no way> to disable the raising of exceptions on
some sort of "global" level.
Mixed Flow Control
Some of the reference texts, when discussing exception
handling, refer to the matter that it may be difficult to
implement a C<go to> across an unwinding semantics block,
as in:
try { open F, $f } catch { next; }
This matter will have to be referrred to the good folks over
in internals. If this functionality is not possible, it can
always be implemented with lexical state variables instead.
Core Functionality
If the guts of Perl 6 are to use exceptions for errors, some
of the stuff in this RFC has to go into the guts of Perl 6.
On the other hand, if Perl 6 decides to stay with just eval,
die, and $@, then the functionality of this RFC can still
be provided by a (core) module, along the lines of that
implemented (with different syntax), in Perl 5, in [TRY-2000].
The actual syntax used would depend on what parser extentions
are available in Perl 6.
Stack Snapshot Object
This RFC mentions that an exception's C<trace> instance variable
could point to a "stack snapshot" object, rather than to a data
structure. A mechanism like this is available in the Perl 5
Devel bundle. If an RFC for such a snapshot class is added to
Perl 6, this RFC should be reconciled with it.
$SIG{__DIE__}
The try, catch, and finally clauses localize and undef
$SIG{__DIE__} before entering their blocks. This behaviour
can be removed if $SIG{__DIE__} is removed.
If $SIG{__DIE__} is not removed, it should be invoked as
at the phrase "program shutdown is initiated" in Rule 1,
not at the time an exception is raised.
If the old functionality is removed, do we want to introduce
such functionality to Rule 1 anyway, and some way to nest
handlers?
RFC 96
Should be withdrawn as it is by the same author as this RFC
and he says it is now covered here.
=head1 IMPACT
Legacy
It is not the intent of this RFC to interfer 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.
Nothing in this RFC impacts the tradition of simple Perl scripts.
C<die "Foo";> continues to work as before.
In fact, as described above, this stuff could all still be done
in a (core) module.
There is no need to use try, catch, or finally, if one doesn't
want to, and in most of the cases where one would want to use
them, it takes less source code with exceptions than with return
code checking (as per the CONVERSION section above).
RFC 63: Exception handling syntax proposal.
--> Impact statement not ready for this draft.
RFC 70: Allow exception-based error-reporting.
--> Impact statement not ready for this draft.
RFC 80: Exception objects and classes for builtins.
--> Impact statement not ready for this draft.
RFC-119: Object neutral error handling via exceptions.
--> Impact statement not ready for this draft.
Traditional eval, $@, and die functionality.
$@ is now always wrapped up in an Exception object, unless
it already isa Exception object. $@ can now only be set by
die, and cleared by eval.
=head1 ACKNOWLEDGEMENTS
This RFC is based on invaluable support on a number of fronts.
This RFC has been refined with the help of (via the perl6 mailing
lists) contributions in particular from Peter Scott, with additional
input from Chaim Frenkel, Graham Barr, and Jonathan Scott Duff, and
via conversations with Jim Hoover.
The Perl 5 implementation of Try.pm [TRY-2000] has been used by the
staff of Avra Software Lab Inc. for production code, it has been
debated in presentations at the Software Engineering Research Lab
at the University of Alberta, and it has been discussed on the
perl-friends mailing list. At one point it was refined based on
the study of Graham Barr's C<Error.pm> module [GBE-1999].
=head1 REFERENCES
ESE-1994: The Encyclopedia of Software Engineering, J.J. Marciniak
(editor), John Wiley & Sons Inc, 1994. ISBN 0-471-54002-1
GBE-1999: Graham Barr's C<Error.pm> module.
http://search.cpan.org/doc/GBARR/Error-0.13/Error.pm
RFC-63: Exception handling syntax proposal.
http://tmtowtdi.perl.org/rfc/63.pod
RFC-70: Allow exception-based error-reporting.
http://tmtowtdi.perl.org/rfc/70.pod
RFC-80: Exception objects and classes for builtins.
http://tmtowtdi.perl.org/rfc/80.pod
RFC-96: A Base Class for Exception Objects
http://tmtowtdi.perl.org/rfc/96.pod
RFC-119: Object neutral error handling via exceptions.
http://tmtowtdi.perl.org/rfc/119.pod
RYK-1989: Computer Architecure, Hardware and Software, R.Y.Kain,
volume 1, Prentice-Hall Inc., 1989. ISBN 0-13-166752-1
TRY-2000: Try.pm version 1.1.3.6, Avra Software Lab Inc., 2000.
http://www.avrasoft.com/perl/rfc/try-1136.zip
=head1 REVISIONS
Version 1, 2000-08-08: Based on Avra Software Lab Inc's Try.pm,
redaction 1.1.3.6.
Version 2 (draft 3), 2000-08-19: Reflects changes made based on
discussion in the various perl6-language lists since 2000-08-08.