[% setvar title Omnibus Structured Exception/Error Handling Mechanism %]

This file is part of the Perl 6 Archive

Note: these documents may be out of date. Do not use as reference!

To see what is currently happening visit http://www.perl6.org/

TITLE

    Omnibus Structured Exception/Error Handling Mechanism

VERSION

  Maintainer: Tony Olekshy <olekshy@avrasoft.com>
  Date: 8 Aug 2000
  Last Modified: 1 Oct 2000
  Mailing List: perl6-language-errors@perl.org
  Number: 88
  Version: 3
  Status: Frozen

NOTES

    RFC 88 as HTML   www.avrasoft.com
    RFC 88 as Text   www.avrasoft.com
    RFC 88 as POD    www.avrasoft.com

    Perl 5 Try.pm    www.avrasoft.com
    Regression Test  www.avrasoft.com

ABSTRACT

"The Encyclopedia of Software Engineering" [ESE-1994] says (p.847):

The structured exception handling mechanism described in this RFC satisfies the following requirements.

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 CONVERSION 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.

DEFINITIONS

raise

propagate

unwinding

trap

cleanly caught

exception

error

DESCRIPTION

The most common forms of structured exception handling are straight- forward. Here they are:

  try { ... } catch { ... }
  try { ... } finally { ... }
  try { ... }
  catch Exception::Foo => { ... }
  catch Exception::Bar => { ... }
  catch                   { ... } # everything else
  finally { ... }

Walkthrough

 throw Exception::IO "a message", tag => "ABC.1234", ... ;
 try { ... } catch <test> => { ... } finally { ... }
 catch { ... }
 catch Exception::DB => { ... }
 catch Exception::DB, Exception::IO => { ... }
 catch <expr> => { ... }
 finally { ... }
 die
 $@ and @@
 eval
 exception 'Exception::Foo::Bar';
 exception 'MyError::App::DB::Foo';

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; }
  try { fragile(); } catch { print "$@\n"; }
  try { ... }
  catch Exception::Foo => { ... }
  finally { ... }

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 { ... }
  try { ... } catch $@->{message} =~ /.../ => { ... }
  try { ... } catch ref $@ =~ /.../ => { ... }
  try { ... } catch grep { $_->isa("Foo") } @@ => { ... }
  try { ... } catch grep { $@->isa($_) } @list => { ... }
  try { ... } catch $@->isa("Foo") && $@->CanBar => { ... }
  try { my $p = P->new; my $q = Q->new; ... }
  finally { $p and $p->Done; }
  finally { $q and $q->Done; }
  try     { TryToFoo; }
  catch   { TryToHandle; }
  finally { TryToCleanUp; }
  catch   { throw Exception "Can't cleanly Foo."; }
  try {
      ...
      $avoidCatches_JustUnwind = predicate();
      ...
      }
  catch $avoidCatches_JustUnwind => { throw $@ }
  catch { ... }
  finally { ... }

Syntax

    <exception> := exception <string> <options> ;

    <throw> := throw <class> <message> <options> ;
             | throw <string> <options> ;
             | throw ;

    <message> := # empty
               | <string>
	       | <string> <options>

    <try> := try <block> <clauses>

    <clauses> := # empty
               | <clause> <clauses>

    <clause> := <catch> | <finally>

    <catch> := catch <block>
	     | catch <classes> <comma> <block>
             | catch <expr> <comma> <block>

    <finally> := finally <block>

    <classes> := <class> | <class> , <classes>

    <class>	# Unquoted name of a class that inherits from the
                # built-in Exception class, such as Exception::Foo.

    <options> := # empty
               | , <string> => <value>
               | , <string> => <value> <options>

    <block> := { ... }	# A Perl code block.

    <comma> := , | =>

    <expr>	# A Perl expression which is evaluated when
    		# the relevant clause is processed.

    <string>	# A Perl expression which is evaluated and
    		# stringified.

Unwinding Semantics

"Computer Architecture, Hardware and Software" [RYK-1989] says (p.347):

Perl's behaviour after a die starts call-stack unwinding, as envisioned by this RFC, is as described by the following rules. The "current exception" is the value of $@.

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 IMPACT + 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 throw Exception "My message.", without a lot of extra tokens, and without getting into higher levels of the taxonomy of exceptions.

Instance Variables

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

message

tag

debug

data

trace

sysmsg

Methods

The built-in Exception base class defines the following methods.

new

throw

overload '""'

settag

tag

show

snapshot

on_raise

on_catch

Custom Exceptions

In addition to the 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';

	use Error;  # It inherits from Exception.

	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);
	    }

Note that the scope of classes like Error::App is is limited to packages that use it, which presumably want such functionality.

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, in Scheme, C++, Visual Objects, Delphi, and Perl. 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]. A similar version of Try.pm is in use in production applications.

The authors' 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):

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 prints? And if you're writing a simple program you wouldn't want to have 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 a failure return code should be expected. And, the exception handling technique not only works with subroutines and functions, it works with operators too (which, you will note, is why divide dies on zero denominator: it has no other way to return an error code).

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 $exception = $@;
	if ($exception) {
	    my $test = eval { may_throw_2 };
	    $@ and $exception = $@;
	    if ( ! $@ and $test ) {
		eval { may_throw_3 };
		$exception = $@;
		}
	    }
	eval { may_throw_4 };
	($exception ||= $@) and die $exception;

the opportunity for flow-control errors increases.

Without the functionality provided by the mechanisms described in this 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 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.

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.

And, of course, if you don't want to use exception objects just don't use throw, instead just keep on using die "string"; as in Perl 5 (because $@ stringifies reasonably).

ISSUES

Perl should be Perl

Core Functionality

New Keywords

Keyword Names

Syntax

Object Model

Exception Base Class

Lexical Scope

Mixed Flow Control

Use %@ for Errors from Builtins

retry / resume

always

eval

catch v/s else + switch

Fatal Exceptions

on_raise & on_catch

Stack Snapshot Object

Mechanism Hooks

Mixed-Mode Modules

$SIG{__DIE__}

IMPACT

Legacy

RFC 63: Exception handling syntax proposal.

RFC 70: Allow exception-based error-reporting.

RFC 80: Exception objects and classes for built-ins.

RFC 96: A Base Class for Exception Objects

RFC 119: Object neutral error handling via exceptions.

RFC 140: One Should Not Get Away With Ignoring System Call Errors

RFC-151: Merge $!, $^E, and $@

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 from Graham Barr, Chaim Frenkel, Jonathan Scott Duff, Glenn Lindermann, Dave Rolsky, and Corwin Brust. It has also benefited from conversations with Jim Hoover.

A slightly different version of 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 Try.pm was refined based on the study of Graham Barr's Error.pm module [GBE-1999].

AUTHORS

Tony Olekshy <olekshy@avrasoft.com> is the principal author of this RFC, and is responsible for any errors contained herein.

Peter Scott <peter@psdt.com> is co-author of this RFC, by virtue of the major contribution he has made hereto.

Where this document refers to the singular author or author's, it refers to Tony. Where it refers to co-author or co-author's, it refers to Peter. Where it refers to the plural authors or authors', it refers to both authors.

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 Error.pm module. search.cpan.org

RFC 63: Exception handling syntax

RFC 70: Allow exception-based error-reporting.

RFC 80: Exception objects and classes for builtins

RFC 96: A Base Class for Exception Objects

RFC 119: Object neutral error handling via exceptions

RFC 140: One Should Not Get Away With Ignoring System Call Errors

RFC 151: Merge $!, $^E, $@ and $?

RYK-1989: Computer Architecture, Hardware and Software, R.Y.Kain, volume 1, Prentice-Hall Inc., 1989. ISBN 0-13-166752-1

TRY-2000: Try.pm, a Perl 5 reference implementation of the functionality described herein, is available at: www.avrasoft.com

REVISIONS

Version 1, 2000-08-08

Version 2, 2000-08-23

Version 3, 2000-09-30

End of RFC 88