=head1 TITLE

    Structured Exception Handling Mechanism

=head1 VERSION

    Maintainer: Tony Olekshy <[EMAIL PROTECTED]>
    Date: 17 Aug 2000
    Version: 2 (Draft 1)
    Mailing List: [EMAIL PROTECTED]
    Number: 88

=head1 ABSTRACT

This RFC describes a collection of changes and additions to Perl,
to support built-in base classes for Exception and Error objects,
and code like this:

    exception 'Error::DB';

    try {
        throw Error::DB => "a message", tag => "ABC.1234", ... ;
        }

    catch "Error::DB" { ... }

    catch [ "Error::DB", "Error:IO" ] { ... }

    trap { $@->{message} =~ /divide by 0/ } catch { ... }

    catch { ... }

    finally { ... }

Any exceptions that are raised within an enclosing try, catch, trap,
or finally block (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 new base class whether or not that happens.

=head1 DESCRIPTION

exception 'Error::DB::Foo';

    Makes Error::DB::Foo into a class that behaves like a built-in
    Exception.

    If the given name matches /::/, something like this happens:

        @Error::DB::Foo::ISA = 'Error::DB';

    If the given name does not match /::/ (say it's just 'DB'), this
    happens instead:

        @DB::ISA = 'Exception';

throw Error::DB => "a message", tag => "ABC.1234", ... ;

    Syntactic sugar for:

        die Error::DB->new(

                message => "a message", tag => "ABC.1234", ...);

    A throw with no arguments is syntactic sugar for re-raising
    the current exception.  If there is no current exception,
    an anonymous Exception is manufactured first.

    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.";

try { ... } catch { ... } finally { ... }

    A try statement starts with a block and is followed by zero or
    more catch and/or finally clauses.

    A catch clause can include a trap clause or other arguments, as
    described below.

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 a trap clause or another catch clause.

    Otherwise, it is semantic sugar for trap { 1 } catch { ... }.

catch "Error::DB" { ... }

    When catch is follwed by a string scalar, this is syntactic
    sugar for:

        trap { $@->isa($string) } catch { ... }

catch [ "Error::DB", "Error:IO" ] { ... }

    When catch is follwed by an listref scalar, this is syntactic
    sugar for:

        trap { grep { $@->isa($_) } @$listref } catch { ... }

trap { ... } catch { ... }

    Traps exceptions for which the trap block returns true.

    It is a syntax error if the catch clause following a trap
    clause has a scalar before its block.

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 isa "UNIVERSAL", wrap it in a new Exception (using
    the "object" instance variable), and raise that instead.

    Otherwise stringify the argument, wrap it in a new Exception,
    (using the "message" instance variable), and raise that instead.

    The above guarantees that exceptions are Exceptions, but because
    of Exception 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:

            $e->{link} = $@;  $@ = $e;  

    This provides the mechanism we use to keep track of raised
    exceptions while unwinding as described below.

$@

    This can *only* be set by die, which guarantees $@ isa
    Exception, as described above.

    Unlike eval, $@ is not cleared when a try statement starts.

eval

    No changes relative to Perl 5.

=head2 Unwinding Semantics

Perl's behaviour after a C<die> starts call-stack unwinding is as
described by the following rules:

  1. Whenever an exception is raised Perl looks for an enclosing
     try/catch/finally block.

     If such a block 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 trap block 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 is cleared.
     If a trap clause or its catch clause raises an exception, it
     becomes the current exception, but it does not propagate out
     of the clause (at this point).

     If a trap block raises an exception or returns true, then
     whether or not the catch clause 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 the try statement completes normally and Perl
     continues with the statement after the try statement.

=head2 Built-In Exception Base Class

In addition to the built-in Exception class described below, a
built-in Error class is defined.  The Error class inherits from
Exception.  Internal Perl assertion failures are instances of
exception classes that inherit from the Error class.  This
results in code like C< catch "Error::IO::File" { } >.  It
also allows simple anonymous error exceptions to be raised with
a form like this:

    throw Error => "message", severity => "Fatal";

The built-in Exception class reserves all instance variable
and method names matching /^[_a-z]/.  The following instance
variables are defined.

tag

    This is a string which module developers can use to assign
    a unique "identifier" to each exception object constructor
    invocation in the module.

severity

    This is some sort of "priority" (such as info v/s fatal) on
    which handing can be based.  The details need to be worked
    out.

message

    This is a description of the exception in language intended
    for the "end user".

debug

    This is a place for additional description that is not
    intended for the end user (because it is "too technical"
    or "sensitive").

object

    If the exception is wrapping an object passed to die, said
    object is saved here.

sysmsg

    This a place for the internal exceptions raised by Perl
    to record system information, along the lines of $!.

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}

    This snapshot is set up by the "snapshot" method, so that
    derived classes that don't want this overhead can override
    the method.

link

    A scalar containing the previous exception ($@) at the time the
    exception object is constructed.  Tracking this 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.

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} = (ref $self) .".". $arg{tag};

overload '""' => sub {

    my $t = exists $_[0]->{tag} ? $_[0]->{tag} . ": " : "";

    exists $_[0]->{severity} and $t .= "($_[0]->{severity}) ";

    $t .= $_[0]->{message} =~ /\S/
        ? $_[0]->{message} : "anonymous exception.";
    }

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:

            trap { $@->tag("Foo") } catch { ... }

any

    This method takes a closure argument.  The given closure is
    invoked for each object on the "exception unwinding stack",
    by following the _link fields in Exception objects, starting
    with $self.

    Each such object is passed to the closure as $_[0].  As soon
    as a closure is found which returns true, this method returns
    true.  If no such closure is found, this method returns false.

snapshot

    Used internally to generate the "trace" instance variable, but
    may be overriden in derived classes.

showStack

    This method generates a string formatted version of the
    exception unwinding stack based on following the "link"
    instance variables, and stringifying each such exception
    object.  C<print $@->showStack> produces something like this:

            TST.1004: Second catch trouble.
            TST.1003: First catch trouble.
            TST.1002: Second trouble.
            TST.1001: First trouble.

    showStack 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::DB: TST.1004: Second catch trouble.
            Error::IO: TST.1003: First catch trouble.
            Error::IO: TST.1002: Second trouble.
            Error::IO: TST.1001: First trouble.

    trace => 1

        If set the value returned by showStack 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:

            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.
            Debug: some debug information.
            TST.1001: First trouble.

=head1 EXAMPLES

The following three examples conver the most common uses of try,
catch, and finally.

    open(*F, ">foo");  try { ... } finally { close F; }

        If foo was opened, close it whether or not the try block
        raised an exception.  If try or close raised an exception
        then propagate the exception after attempting to close
        the file.

    try { fragile(); } catch { print $@->showStack; }

        If fragile() raises an exception it is shown on STDOUT.
        No exception is propagated, unless the catch block raises
        an exception.

    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 catch or finally raised an exception is it propagated,
        but if try raised an exception and catch didn't then no
        exception is propagated (because the exception was cleanly
        caught).

Most developers will usually only find needs for the three cases
shown above.  The following examples can be used when circumstances
merit, but they should be avoided by those looking from maximal
simplicity.

    try { ... }
    catch "Error::IO" { ... }
    catch "Error::DB" { ... }
    catch             { ... }

        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.

        No exception is propagated unless one of the catch
        blocks raises an exception.

    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 { } trap { $@->isa("Foo") and $@->CanBar } catch { }

        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.

    try { } trap { $@->{message} =~ /.../ } catch { }

        Any exception object instance variable can be used
        to test whether or not the exeption should be caught.

    try { } trap { ref $@ =~ /.../ } catch { }

        Catches the current exception if has a class name that
        matches the given regular expression!

    try { } trap { $@->any(sub{ $_[0]->isa("Foo") })} catch { }

        The catch clause is invoked if any exception (that can be
        found by following the link fields of Exception objects)
        isa Foo.

    try     { TryToFoo; }
    catch   { TryToHandleFailure; }
    finally { TryToCleanUp; }
    catch   { throw Error => "Can't cleanly Foo."; }

        Propagates a new exception if any of the first three
        blocks throws.  This can result in 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.

    try {
        $avoidCatches_JustUnwind = predicate();
        }
    trap { $avoidCatches_JustUnwind } => catch { 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).

=head1 MOTIVATION

The author's motivation is to provide a relatively robust exception
handling mechanism that is suitable for error handling via exceptions,
yet is still cabable enough to handle the needs of production programs.

To this end, new keywords have been chosen to represent the new
mechanism, in order to make it clear 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.

Although the following code using the new mechanism:

    try     { may_throw_1 }
    trap    { may_throw_2 } => catch { 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 ISSUES

RFC 96

    Should be withdrawn as it is by the same author as this RFC
    and is now covered here.

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.

Keyword Names

    RFC 88 only introduces the try, throw, trap, catch, finally,
    and exception keywords, which are all traditionally related
    to exception handling.  Also, "try" was chosen because of it
    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 $@; }

    instead of overloading existing keywords in a manner like this:

        eval {
            eval {
                eval     { ... }
                continue { ... }
                }
            else {
                switch ($@) {
                    case /First/  { ... }
                    case /Second/ { ... }
                    else          { ... }
                    }
                }
            }
        else { print $@; }

    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, or when
    an "else throw" is forgotten on a switch that needs to re-throw.

    The "try" is not necessarily for Perl's sake.  It's for the
    programmer'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).

    The "trap" is used to prevent a two-block catch, which can be
    hard for humans to parse.

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 should be left in.

    This also means you can clear the current exception with eval,
    even though you can't set $@ directly.

Exception Base Class

    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?

$SIG{__DIE__}

    The try, catch, and finally clauses localize and undef
    $SIG{__DIE__} before entering the blocks.  This behaviour
    can be removed if $SIG{__DIE__} is removed.

=head1 IMPACT

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.

Traditional eval, $@, and die functionality.

    $@ is now always wrapped up in an Exception object, unless
    it already isa Exception object.  $@ can only be set by
    die, and cleared by eval.

=head1 REFERENCES

RFC 63: Exception handling syntax proposal.

RFC 70: Allow exception-based error-reporting.

RFC 80: Exception objects and classes for builtins.

RFC 96: A Base Class for Exception Objects

=head1 REVISIONS

Version 1, 2000-08-08: Based on Avra Software Lab Inc's Try.pm,
redaction 1.1.3.6.

Version 2 (draft 1), 2000-08-17: Reflects changes made based on
discussion in the various perl6-language lists since 2000-08-08.

Reply via email to