It is a well known perl idiom to write exception handling with eval
and die
, instead of try
and catch
.
eval {
do_something()
or die "Err!";
};
if ($@) {
print "Catched exception: $@";
}
Even Damian Conway does it in chapter 16 of Perl Best Practices and ‘perldoc -f die‘ does it too. Unfortunately it doesn’t work all the time. Consider the following situation:
#!/usr/bin/perl
package Foo;
sub new {
my $class = shift;
return bless {}, $class;
}
sub DESTROY {
my $self = shift;
eval { 1; };
}
package main;
eval {
my $foo = Foo->new();
open my $fh, ">", "/"
or die "Could not open /: $!";
print "Doing some work\n";
};
if ($@) {
print STDERR "Something bad happened: $@\n";
} else {
print "Everything went well\n";
}
As we shouldn’t be able to open ‘/’ for writing, we expect to see an error message. But it turns out that the script claims to succeed even tough it didn’t do any “work”. So what happened? Well, $foo went out of scope, the destructor was called and the embedded eval changed $@.
What can we do to solve the problem. First of all, make sure that the destructor doesn’t chage $@. You can do this by localizing it. Even tough you’re not calling eval directly, some other code might do it. So always start destructors like this:
sub DESTROY {
my $self = shift;
local $@;
...;
}
By the way, you want to localize $? too, but that is another story.
Another solution is to change you try/catch emulation. If an eval’ed block dies, the resulting value is undef. So make sure that the ordinary case always ends with a true value, and replace catch with a simple or:
eval {
do_work();
1;
} or do {
print "catch: $@";
};
Of course $@might be misleading about the real error, so this doesn’t work as well as fixing the destructors. But when using modules written by others, you might implement defense in depth by using both solutions. I think there is a Perl::Critic policy advocating the above try/catch construction.
But how does the execption modules on CPAN fare against the above destructor? I tried two of them: Error.pm works correctly, because it’s throw
implementation stores the exception before dying. The much newer TryCatch.pm doesn’t work (See rt #46294), but is much cooler otherwise.
Update: I’ve managed to write a fix for TryCatch.pm. It includes a throw function, just like Error.pm.