Unexceptional Exceptions in Perl 5.14

There's a lot to love about Perl 5.14, but one of the best changes is a subtle one: Native exception handling is now reliable. Perl's native exception syntax uses an eval block as the traditional "try" block, and requires checking $@ to see if it contains an exception.
  eval {
    ...; # do something that might throw an exception
  };
  
  if ($@) {
    ...; # handle exception here
  }
Just like other languages code in the block is evaluated until an exception is thrown and then control jumps out of the block. Perl doesn't have a native catch syntax however - it simply puts the exception into $@ where you can check it with a standard if statement. Herein lies the problem in all versions of Perl prior to 5.14. Prior to 5.14 $@ is assigned and then the block is immediately exited; With 5.14 the block is immediately exited and then $@ is assigned. A subtle, but important difference. Perl's improvised catch mechanism relies on eval undefining $@ if no exception occurred (so the if block won't execute.) Each time Perl executes an eval therefore it must undefine $@. Prior to 5.14 this interacts badly with object destructors.
  package SomethingComplex;
  sub new { return bless {}, shift };
  sub DESTROY {
    eval {
      ...; # some cleanup code that might throw an exception
    };
    if ($@) {
      ...; # handle exception in cleanup code
    }
  }
  
  package main;
  
  eval {
    my $thingy = SomethingComplex->new();
    ...; # do something that might throw an exception
  };
  if ($@) {  
    ...; # will never be executed even on exception
  }
If an exception occurs in the eval block in main then execution will stop and control will immediately jump out of the block. $thingy will fall out of scope. When this happens the object's DESTROY block will be executed. This in turn runs its own eval which will unset $@ as it executes. Assuming another exception doesn't occur during cleanup by the time we reach the if statement in main $@ will have been undefined even though an exception happened in the eval block immediately above. Disaster! The, simple, quite frankly terrible, workaround is to write this:
  eval {
    ...; # do something that might throw an exception
    1;
  } or do {
    ...; # handle the exception here
  }
We're no longer relying on the $@ to tell that an exception has occurred but on the fact that an eval block quill return false on exception handling. Of course, we can't now reliably look at $@ to find out what kind of exception occurred. There's ways around this, but they're even more complex to code. A better fix on Perl's prior to 5.14 is to use the Try::Tiny module from the CPAN that handles all of this for us.
  use Try::Tiny;
  try {
    ...; # do something that might cause an exception.
  } catch {
    ...; # handle the exception stored in $_ here
  };
Of course, no matter how tiny Try::Tiny is, there's no getting away from the fact that it's not a module that's bundled with Perl; I can rely on getting it installed whenever I install software, but not on every machine I might happen to admin and want to make use of the system perl. Luckily, Perl 5.14 solves this problem entirely for us by executing the block first - therefore executing all destructors that might mess with $@ first - and then, once all that's done, populating $@ with the exception. Thanks Perl 5 Porters!

- to blog -

blog built using the cayman-theme by Jason Long. LICENSE