This and other RFCs are available on the web at
  http://dev.perl.org/rfc/

=head1 TITLE

Structured Exception Handling Mechanism (Try)

=head1 VERSION

    Maintainer: Tony Olekshy <[EMAIL PROTECTED]>
    Date: 08 Aug 2000
    Version: 1
    Mailing List: [EMAIL PROTECTED]
    Number: 88

=head1 ABSTRACT

This RFC considers the addition to Perl of the exception handling
verbs B<try>, B<throw>, B<except>, B<catch>, B<finally>, and
B<unwind>.  Its exception handling mechanism can be extended
without adding new verbs.  It also introduces the concept of
exception stack handling while unwinding.

This RFC does not constrain the details of exception objects per
se, but it does define the operations that may be performed on
such objects by the new verbs.

A zip of Try.pm, a Perl 5 implementation of the functionality
described hereunder, complete with a set of regression tests,
is available at http://www.avrasoft.com/perl/rfc/try-1136.zip
To exercise the regression tests, run the "regress" script.

=head1 DESCRIPTION

Various implementations of exception handling mechanisms for
Perl have (over the years) used eval, die, $SIG{__DIE__}, and $@
in incompatible ways.  This has lead to a legacy situation that
is impeding the development of robust modules, frameworks, and
applications.

In addition, all of Perl's extant exception handling modules
(to the author's knowledge) overwrite the information on the
exception they are processing whenever an exception is raised
during said processing.  Therefore, during exception handling,
no information is available on any but the last of the set of
exceptions being handled during the current unwinding.  There
is no way to ask, "Was exception Foo part of what's going on?"

Loosing track of exceptions while processing them also means
that should some client need to be informed of the exceptions
being processed, only one level of context information is
available, such as: "Can't add a new person to the database."
However, if the set of exceptions being processed is available,
one can arrange to get information like this:

        UIM.1234: Can't add a new person to the database.
        APP.2345: Can't update Company relationship.
        DBM.3456: Trouble processing SQL UPDATE clause.
        DBM.4567: Unable to write to Company table.
        IOM.5678: Can't open file ".../company.db".
        IOM.6789: Access to ".../company.db" denied.

The Try.pm module described hereunder provides a solution to the
procedural part of the structured exception handling problem,
that is, to the syntax and semantics of verbs like try, throw,
catch, and finally.  As far as Try is concerned, the nouns
involved (the actual exceptions passed to throw or die) can be
any Perl datum.

A valid try statement looks like this:

    try { ... throw ... }               #  try clause

        except TEST => catch { ... }    #  catch clause

        finally { ... }                 #  finally clause

        unwind { ... };                 #  unwind clause

There can be zero or more catch clauses and finally clauses; all
finally clauses must come after any catch clauses.  There can be
zero or one unwind clause.  The "except TEST" part of a catch
clause is optional.  The available TESTs are disussed later
hereunder.

The clauses are evaluated in left-to-right order.  Try traps and
keeps track of any exceptions raised by throw or die under any
clause, and proceeds as follows:

  * The try clause is evaluated.

  * Each catch clause is invoked, but only if an exception has
    been raised since the beginning of the try statement, and
    the catch clause's except TEST (if any) is true.

  * Each finally clause is invoked whether or not an exception
    has been raised since the beginning of the try statement.

  * The unwind clause, if any, is invoked if an exception has
    been raised since the beginning of the try statement, and
    it has not been cleanly caught.

  * After processing all clauses, try unwinds (dies) iff any
    exception wasn't cleanly caught.

Here are five simple examples that illustrate some of the core
functionality implemented by Try.

=over 4

=item *

Catch All Exceptions

    use Try;

    try { ... }

    catch { ... };

=item *

Catch Exceptions by Message Text

    my ($x, $y, $z) = ( ... );

    try {
        $x = $y / $z;
        }
    except "division by 0" => catch { $x = undef; };

=item *

Catch Exceptions by Class

    try {
        structured_io_operation;
        }
    except isa => "IO:Error" => catch { ... };

=item *

Clean Up Even If Unwinding

    my $o;

    try {
        f( $o = SomeClass->New );
        }
    finally { $o and $o->Done; };

=item *

Handle Complex Nested Trys

    try {
        try     { throw "First"; }
        finally { throw "Second"; }
        }
    except First  => catch { print "Caught First\n"; }
    except Second => catch { print "Caught Second\n"; };
    except else   => catch { throw "Something blew up."; };

(Note that this case can't be handled without an unwind stack,
because throw "Second" overwrites exception "First", so except
First doesn't match.)

=back

Try's mechanism works with exceptions raised via either throw or
die.  Although a minimalist exception object base class (Try::Except)
is provided as part of the Try module, you are free to concurrently
use your own exception classes with Try, or to simply use string
exceptions, including those generated by Perl's "die" function.
This approach respects Perl's TMTOWTDI tradition.

The factoring of this problem into its nouns and verbs is useful
because of the way it allows Try to handle multiple independent
exception class hierarchies.  The only operations Try performs on
exception nouns are stringification and isa.  Nevertheless, via
"except TEST" expressions, Try provides a mechanism by which any
other first-class exception object methods can be used as the basis
for complex exception handling.

Try's mechanism can be used without any reference to eval, die, $@,
or $SIG{__DIE__} on the client side of its API, so the addition to
Perl of a mechanism like Try could leave those constructs unchanged,
to the benefit of legacy applications.  In addition, until Perl has
a built-in explicit exception handling mechanism, Try's working
implementation can be used instead (since it interacts reasonably
well with Perl's legacy constructs).

=head1 PRINCIPLES OF OPERATION

=head2 Statement Syntax

Try installs six subroutines into the package from which it is
used, namely: try, throw, except, catch, finally, and unwind.
The try, catch, finally, and unwind subroutines are prototyped
as &@, so you can write "try { ... }" instead of having to
write "try sub { ... }".

The complete syntax of a valid try "statement" is as follows,
where ? is zero-or-one, * is zero-or-more, and + is one-or-more.

    <try> := try { ... } <catch-clause>*
                         <finally-clause>*
                         <unwind-clause>?
                         ;

    <throw> := throw <exception> { ... => ... }? ;

    <exception> := <object> | <string>+

    <catch-clause> := catch { ... }
                    | except catch { ... }
                    | except <test> => catch { ... }

    <finally-clause> := finally { ... }

    <unwind-clause> := unwind { ... }

    <test> := isa => <string>
            | any => sub { ... }
            | else
            | check
            | sub { ... }
            | <re>
            | <string>

                When parsing <test>s, the first keyword match
                found (in the order shown) wins, and the
                following arguments are parsed according to it.

    <string>    The next scalar value in try's argument list,
                which is stringified.  <string>+ means a list
                of comma-seperated strings.

    <object>    A blessed reference.

    <re>        A compiled regex, typically of the form qr/.../

    { ... }             The body of a closure.

    { ... => ... }      A hash reference of named option values.

=head2 Statement Semantics

The except, catch, finally, and unwind subroutines wrap up their
arguments to build a list for try.  The try subroutine processes
said list while handling exceptions and unwinding.  The Try module
keeps (in module-scoped lexical variables) a shadow unwind stack
and some other state information.

The throw subroutine raises an exception, very much like
die (but it knows one extra thing: it's not a legacy die
invocation).  A throw with no arguments just unwinds without
stacking another exception.  If the Debug option is specified
in an invocation of throw, its value is tracked on a parallel
debug stack, which may be used by Try::ShowStack to show
additional context information.

The try subroutine localizes and undefs $SIG{__DIE__}, evals its
closure, and post-processes $@ and Try's state information to
push a raised exception, if any, onto the unwind stack.  The try
subroutine returns the last value in its closure (if it doesn't
unwind).

Any catch and finally clauses are processed, in order, before
try returns.  The closures of catch clauses are only invoked if
an exception has been raised since the beginning of the try
statement, and the catch clause's except TEST (if any) evaluates
to true (as described below).

Each finally clause is invoked whether or not an exception has
been raised since the beginning of the try statement.

The unwind clause, if any, is invoked only if an exception has been
raised and not cleanly handled.  The unwind clause is provided to
allow you to collapse two nested try clauses when the only purpose
of the outer clause is to catch unwinding from the inner clause no
matter how it failed.  For example:

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

Exceptions raised in try, catch, finally, and unwind clauses,
and in TESTs, are trapped by Try, are tracked on its unwind
stack, and are available to the except TEST expressions.

After processing all clauses, try unwinds (dies) iff the
processing of any clause raised an exception, unless it was
cleanly caught.  Cleanly caught means the exception was in the
try clause, and it triggered a catch clause, and no catch or
finally clause raised an exception.  If try does not unwind,
it clears the unwind stack.

Whenever throw or try unwinds, the argument it passes to die is
a string-formatted version of the unwind stack.  Therefore,
while unwinding, $@ is not "", and if we don't end up handling
the exception somewhere, Perl (or someone else's $SIG{__DIE__})
will see something useful in the contents of $@.

While processing its argument list, the try subroutine performs
sanity checks on the values it has received.  For example,

    try { } except isa => "Class" => finally { };

stacks the following exception and unwinds:

    TRY.1030: Expecting "catch" clause but found "finally".

=head2 Exception Tests

The Try module supports a number of except TEST expressions that
can be used to select which catch clauses are to be invoked when
an exception has been raised.  These tests have been selected to
support Perl's DWIM tradition, and to make easy easy and hard
possible.  Each type of qualified catch clause is shown here in
context.

    catch { ... }

        This clause is invoked if the unwind stack is not empty.
        It is typically used, in this unadorned form, to catch
        and handle all possible exceptions, as in:

            try {
                }
            catch { print Try::ShowStack; };

    except "..." => catch { ... }

        This clause is invoked if there is an item on the unwind
        stack which, when stringified, matches /\Q...\E/, for
        example:

            try {
                $x = $y / $z;
                }
            except "division by 0" => catch { $x = undef; };

        To search for a word of the form /^[_a-z][_a-z0-9]*$/
        (that is, a lower case identifier) you must use a TEST
        of the form except qr/.../ => catch { ... }, as
        described below, because all such words are reserved
        for current or future use in except TEST expressions.
        An exception will be raised if you don't.

    except qr/.../ => catch { ... }

        This clause is invoked if there is an item on the unwind
        stack which, when stringified, matches /.../, for
        example:

            try {
                $x = $y / $z;
                }
            except qr/div.* by 0/ => catch { $x = undef; };

    except isa => "ClassName" => catch { ... }

        This clause is invoked if there is an item on the unwind
        stack which is a reference and which passes the test
        $item->isa("ClassName"), for example:

            try {
                structured_io_operation;
                }
            except isa => "IO:Error" => catch { ... };

    except any => sub { ... } => catch { ... }

        The sub closure is invoked for each object on the unwind
        stack, passing said object in as $_[0].  As soon as an
        object is found for which the sub returns true, the
        catch closure is invoked and the clause is done.  If no
        object satisfies the sub closure, the catch closure is
        not invoked.

        This type of catch clause can be used to invoke
        arbitrary test methods on stacked exception objects,
        as in:

            try {
                }
            except any => sub { $_[0]->isa("Foo")
                             && $_[0]->SomeTest }
                => catch { };

        In addition, a literal or variable reference to a named
        or anonymous closure can be used instead of sub { },
        as in:

            try {
                }
            except any => \&MyTest => catch { };

    except sub { ... } => catch { ... }

        The sub closure is invoked, and if it returns true the
        catch closure is invoked.  The sub closure is passed a
        copy of the current unwind stack in @_.

        This provides a generic way to perform tests against the
        state of the world, as in:

            try {
                }
            except sub { f( @_ ) } => catch { };

        In addition, a literal or variable reference to a named
        or anonymous closure can be used instead of sub { },
        as in:

            try {
                }
            except \&MyTest => catch { };

    except check => catch { ... }

        The catch closure is invoked if the unwind stack is not
        empty.  It is passed a copy of the unwind stack in @_.

        If the closure returns true (without raising an
        exception), then the exception is considered to be
        caught (for the purposes of unwinding) otherwise it is
        not.

        This form of except TEST expression can be used when it
        is not convenient to seperate the test for an exception
        from the catching thereof, as in:

            try {
                }
            except check => catch { f( @_ ) ? 1 : 0 };

    except else => catch { ... }

        If the try clause threw, and no catch clause before the
        except else clause has been triggered and successfully
        completed, then the catch closure is invoked.  This
        provides a convenient way to say things like:

            try {
                }
            except "something known" => catch { ... }

            except else => catch { something_unknown; };

=head2 Exception Objects

The throw subroutine accepts any Perl datum as an exception,
including a reference to something blessed into a descendent of
UNIVERSAL.  In the latter case, an except isa => "ClassName"
expression can be used to select relevent catch clauses.
Objects passed via die (in recent versions of Perl) are also
handled as objects.

The Try module contains a minimal exception object base class
called Try::Except.  You can use it as is.  You can derive your
own classes from it.  You can also ignore it--nothing else in
Try cares, and it doesn't pollute the namespace.

Try::Except objects have one publically visible Message instance
variable.  Try::Except->New takes an argument and sets said ivar
to it.  Stringification returns said ivar.  A Throw constructor
is also provided for your convenience, it just does:

                throw( $_[0]->New($_[1]) );

By design, you can implement your own exception class hierarchy
and use it with Try's structured exception handling mechanism.
The only operations performed on your objects are stringification
and isa.  Your classes will play well with other exception
classes that define a useful stringification, and with unadorned
dies (including those in legacy modules and the Perl core).

Internally, Try actually converts non-object exceptions into
instances of the following classes, which you can test against, or
(because of stringification) you can ignore.  These classes are all
derivatives of Try::Except.

    Try::Except::Error

        Trouble messages relating to try's syntax are wrapped up
        as objects of this class.

    Try::Except::Throw

        If throw is passed a non-object exception, it is
        stringified and wrapped up as an object of this class.

    Try::Except::Die

        If Try detects that an exception was raised by die
        rather than by throw, and said exception is not already
        an object, then the exception is stringified and wrapped
        up as an object of this class.

=head2 Unwind State Accessors

In application and framework environments it is normal to wrap
user events in an outer try that looks like this:

    try {
        handle_user_event;
        }
    catch {
        show_trouble( Try::ShowStack );
        };

The Try::ShowStack subroutine returns a string-formatted copy
of the current unwind stack suitable for printing. For example:

    try {
        try {
            try     { throw "TST.1001: First trouble."; }
            finally { throw "TST.1002: Second trouble.",
                            {Debug => "Debug Second."};
                    }
            }
        except First  => catch {
                 throw "TST.1003: First catch trouble."; }
        except Second => catch {
                 throw "TST.1004: Second catch trouble."; };
        }
    catch { print Try::ShowStack; };

prints:

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

Try::ShowStack takes the following optional parameters:

    Label => 1

        If set the formatted unwind stack is annotated with
        the classes of the objects therein, as in:

            Try::Except::Throw: TST.1004: Second catch trouble.
            Try::Except::Throw: TST.1003: First catch trouble.
            Try::Except::Throw: TST.1002: Second trouble.
            Try::Except::Throw: TST.1001: First trouble.

    Trace => 1

        If set the value returned by ShowStack includes the Perl
        stack traceback as at the last leaf-level throw (that
        is, a throw or die when the unwind stack is empty), as
        in:

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

            Try::throw called from test-403.pl[7].
            Try::try called from test-403.pl[9].
            Try::try called from test-403.pl[11].
            Try::try called from test-403.pl[14].

    Debug => 1

        Annotates entries in the unwind stack with the values
        from the Debug option in the throw statements, if any.
        For example:

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

The following package variables also affect the output produced
by Try::ShowStack.

    $Try::Debug = 1

        Shows all subroutines in Perl's stack traceback,
        including those internal to Try itself, as in:

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

            Try::__ANON__ called from Try.pm[384].
            Try::throw called from test-403.pl[7].
            main::__ANON__ called from Try.pm[134].
            (eval) called from Try.pm[134].
            Try::__ANON__ called from Try.pm[157].
            Try::try called from test-403.pl[9].
            main::__ANON__ called from Try.pm[134].
            (eval) called from Try.pm[134].
            Try::__ANON__ called from Try.pm[157].
            Try::try called from test-403.pl[11].
            main::__ANON__ called from Try.pm[134].
            (eval) called from Try.pm[134].
            Try::__ANON__ called from Try.pm[157].
            Try::try called from test-403.pl[14].

    $Try::Devel = 1

        Attempts to use the Devel::DumpStack module to create a
        Perl stack traceback that includes subroutine argument
        values, as in:

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

            $ = Try::throw('TST.1001: First trouble.')
                called from test-403.pl[7].
            $ = Try::try(CODE(0xca9054), 'finally', CODE(0x10b7118))
                called from test-403.pl[9].
            $ = Try::try(CODE(0xca9114),
                    'except', 'First', 'catch', CODE(0x10b7650),
                    'except', 'Second', 'catch', CODE(0x10b76bc))
                called from test-403.pl[1].
            $ = Try::try(CODE(0xcafc94), 'catch', CODE(0x10b7764))
                called from test-403.pl[4].

In addition to Try::ShowStack, Try provides three other accessor
functions for internal state information.  Try::UnwindStack
provides a copy of a list of all the exceptions on the unwind
stack, most recent first.  Try::DebugStack does the same for the
values of Debug options passed to throws.  Try::TraceBack
provides a string formatted version of the Perl stack traceback
as at the time the first exception occured.  These accessors can
be used in except TEST expressions, and they can be used to
implement a custom Try::ShowStack (such as not showing debug and
stack traceback info to the end user, but logging it for
admins).

=head1 IMPLEMENTATION

The Try module approach could provide an evolutionary path via which
Perl 5 programs can grow to use the new Perl 6 mechanism before Perl
6 is ready, via the Perl 5 implementation of Try.pm.

Adding structured exception handling to the core would provide a
clean set of verbs for functionality that is currently implemented
via ad hoc uses of eval, die, $@, and $SIG{__DIE__}.  Also, changes
to the internal use of exception objects, by Perl, can be coordinated
with the new verbs.

Since Try doesn't expose eval, die, $@, or $SIG{__DIE__}, Perl 6
could make those mechanisims invisible to Perl programs, but use
them (or something like them) internally, to implement structured
exception handling.  This provides a way to grow out of the current
incompatible uses of eval, die, $@, and $SIG{__DIE__}.

Adding structured exception handling to the core would also result
in run-time speedups, since Try's clause-list-building and handling
would not have to be done in run-time Perl.

Finally, as core functionality, a sanctioned structured exception
handling mechanism can be the place where Perl defines the standard
requirements for compatible exception object classes.

Alternatively, in the name of I<Perl should stay Perl>, Perl 6
could just throw out some of the worst legacy abuses (such as
$SIG{__DIE__}), and Try.pm could remain a module (or, a core
module).  In this case all that is required is to make sure that
Try.pm can be implemented in Perl 6.

=head1 ISSUES

If Try's notions of the seperation of exception handling from the
implementation of exception objects gains credence, there will need
to be one or more RFCs on the matter of the built-in or core-module
Exception class sanctioned by Perl 6.  For example, the exception
texts used herein often contain a source-code-locator like ABC.1234.
The author would like to see some such functionality make it into
a core base class for Exception objects.

=head1 ACKNOWLEDGEMENTS

The current implementation of Try has been refined by studying
Graham Barr's C<Error.pm> module, via discussions on the
perl-friends mailing list, through use by the staff of Avra
Software Lab Inc., and through discussion at the Software
Engineering Research Lab at the University of Alberta.

Previous versions of structured exception handling with unwind
stack functionality have been developed by Mr. Olekshy in Scheme,
C++, Visual Objects, and Delphi.

=head1 REFERENCES

=over 4

=item *

A zip of Try.pm, a Perl 5 implementation of the functionality
described hereunder, complete with a set of regression tests,
is available at http://www.avrasoft.com/perl/rfc/try-1136.zip
To exercise the regression tests, run the "regress" script.

=back

=head1 REVISIONS

=over 4

=item *

Version 1, 2000-08-08

Based on Avra's Try.pm redaction 1.1.3.6.

=back



Reply via email to