[% setvar title Exception handling syntax %]

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

Exception handling syntax

VERSION

  Maintainer: Peter Scott <peter@psdt.com>
  Date: 8 Aug 2000
  Last Modified: 23 Aug 2000
  Mailing List: perl6-language-errors@perl.org
  Number: 63
  Version: 4
  Status: Retired
  Replaced-By: 88

ABSTRACT

This RFC has been merged into RFC 88. The text of the last version prior to the merge is left below for archival purposes only. Anyone interested in browsing this for historical reasons probably has way too much time on their hands :-)

This RFC proposes an exception handling syntax for Perl 6. It was originally adapted from Graham Barr's Error.pm module, but it's been modified enough since then that he might not want the blame by association :-)

DESCRIPTION

An exception handling mechanism is proposed with the following syntax:

       exception EXCEPTION

       try BLOCK catch [EXCEPTION [, ...] ] BLOCK ...
       try BLOCK catch [EXCEPTION [, ...] ] BLOCK ... continue BLOCK
       try BLOCK catch EXPR BLOCK ...
       try BLOCK catch EXPR BLOCK ... continue BLOCK
       try BLOCK continue BLOCK

       throw EXCEPTION [ (attributes) ]
       throw EXPR

Flow of control

1. When an exception is thrown perl looks for the enclosing try block; if there is none then program dies with the text in the message attribute of the exception, which is what the exception stringifies to.

2. If there is an enclosing try block perl goes through the associated catch blocks in order. If the catch criteria succeed (the exception class matches one in a list, or a catch expression evaluates to true, or the catch block catches everything), the catch block is executed. If the catch block throws an exception, it becomes the 'current' exception (with a link to the previous one), otherwise there is no longer a current exception.

3. Whether or not a catch block was executed, the continue block is now executed if there is one. If the continue block throws an exception, it becomes the 'current' exception (with a link to the previous one if there was one). At this point, if there is a current exception, go to step 1.

The exception object is passed into each block in $_[0].

Note that try is a keyword, not a function. This is solely so that a ; is not needed at the end of the last block, since so many people are bound to think it shouldn't be there and forget.

try, catch, and continue blocks share the same lexical scope, in the same way that while and continue currently do. This is so that variables (e.g., filehandles) defined in the try block can be operated on in the other blocks.

There is debate over whether try should be eval so as not to use another keyword. Since it has different semantics (rethrows uncaught exceptions), it should not be eval; but if this happens, then surely eval BLOCK should be retired since it is so similar there would be confusion over what to use for exception handling. And no-one would get the semicolons right :-)

While try and catch are new keywords, throw and exception don't need to be; they're just functions.

Exceptions

Exceptions are objects belonging to some Exception class. throwing an exception creates the object; therefore, EXCEPTION above is just a class name (possibly including some ::).

The exception function is just syntactic sugar for creating a new exception class;it merely amounts to @EXCEPTION::ISA = 'Exception'.

throw takes as argument either an exception class and attributes (could be implemented as a class method with indirect object syntax) or an expression evaluating to an exception object (could be implemented as core function).

Exception classes - rethrowing

RFC 80 proposes standard exception classes and methods for core exceptions. This RFC doesn't need to repeat those, but it can expound upon the semantics that exception classes ought to have. Assume wlog that they all inherit from a base class, Exception. Some people think that exceptions should not be rethrown implicitly. We can provide a boolean flag in the class or even the exception object itself to be checked for this:

      sub Exception::IO::implicit_rethrow { 0 }        # One class
      sub Exception::implicit_rethrow     { 0 }        # All classes
      throw Exception::Socket(implicit_rethrow => 0);  # Just this exception

[Note: it had been proposed that this functionality would be provided by a method uncaught_handler which would be called if an exception wasn't caught. But if that routine decided to rethrow the exception, the continue block would not get called at the right time. However, there are objections to the "action-at-a-distance" semantics of the above proposal.]

Exception classes - ignoring

Note that we could also make it possible to selectively or globally ignore exceptions, so that perl continues executing the line after the throw statement. Just import a ignore function that takes a class name:

      ignore Exception::IO;     # Ignore all I/O exceptions
      ignore Exception;         # Ignore all exceptions

and when perl sees the throw, it just does nothing. (Or do it by overriding a base class method as for implicit_rethrow if you don't want to put another function in the namespace.) Since throw and die should be essentially identical, this would allow any kind of exception to be ignored, not just the ones that were thrown. There is violent opposition to this proposal; I should note, though, that even if it were not implemented, it might well be possible not to avoid people doing it by overriding the throw function in one or more exception classes.

Examples

These examples assume some core exception classes, although the mechanism can be implemented even if core exception classes aren't. The nature of exceptions is that they are most useful on lengthy code; code brief enough to be a readable example also makes a functionally poor example.

       exception Exception::MyDB;
       sub foo {
           open_database();
           # database-munging code
           throw Exception::MyDB(message => 'permission denied');
           close_database();
           open_network_connection();
           # network munging code
           throw Exception::IO(message => 'timeout');  #  In Socket.pm, perhaps
           close_network_connection();
           }

       try {
           foo();
       } catch Exception::MyDB, Exception::DBI {
           close_database();
           # other stuff
       } catch Exception::IO {
           close_network_connection();
           # other stuff
       } catch {
           my $err = shift;
           warn "Some other kind of error in foo: $err\n";
           throw $err;      # Re-throw exception (just 'cuz we wanted to)
       }

IMPLEMENTATION

A throw is just a die passed an object that stringifies to the message you will die with if nothing catches the exception; therefore users can use code that throws exceptions without using the exception-handling mechanism. They should also be able to wrap a try block around code that doesn't know about exceptions but does call die, and have that caught as well; perl should turn the die text into an exception object with the same text, in the base Exception class.

$SIG{__DIE__}

Many people want <$SIG{__DIE__}> to be axed altogether, since it fires immediately when anything throws an exception, rather than being a handler of last resort. If a try block implicitly does local $SIG{__DIE__}, it could protect itself, though. So this RFC doesn't

NOTES

This RFC is very similar to RFC 88, which has slightly different syntax and adds functionality for adding exceptions to a stack. While this author considers that to be going a bit too far (he's tried it before and found he never made use of anything under the top level of the stack), he wouldn't mind if RFC 88 got adopted instead.

This RFC is attempting to stake out a more minimalist position. Some people think it should be even more minimal by using eval instead of try, and an else instead of multiple catch blocks. One advantage of their approach is that you can use an expression for the exception you wish to catch in a switch block:

      eval { ... }
      else {
          switch ($@) {
              case ^_->isa('Exception::IO') { ... }
              case ^_->my_method            { ... }
      }

Now in this case, the problem arises, how should the code implicitly rethrow uncaught exceptions? Without having to look inside the 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 undef $@ at the end of every successful <case> block, so that perl rethrows any $@ still extant at the end of the else. This is a quite excellent suggestion that should be considered if this proposal is evaluated as requiring too many new keywords: our objection to it is that it requires the programmer to insert undef $@ in every instance of by far the the most common type of clause.

REFERENCES

Error.pm (search.cpan.org)

RFC 80: Exception objects and classes for builtins

RFC 88: Structured Exception Handling Mechanism (Try)