What's broken about exceptions in Perl?
Asked Answered
C

8

30

A discussion in another question got me wondering: what do other programming languages' exception systems have that Perl's lacks?

Perl's built-in exceptions are a bit ad-hoc in that they were, like the Perl 5 object system, sort-of bolted on as an afterthought, and they overload other keywords (eval and die) which are not dedicated specifically to exceptions.

The syntax can be a little ugly, compared to languages with builtin try/throw/catch type syntax. I usually do it like this:

eval { 
    do_something_that_might_barf();
};

if ( my $err = $@ ) { 
    # handle $err here
}

There are several CPAN modules that provide syntactic sugar to add try/catch keywords and to allow the easy declaration of exception class hierarchies and whatnot.

The main problem I see with Perl's exception system is the use of the special global $@ to hold the current error, rather than a dedicated catch-type mechanism that might be safer, from a scope perspective, though I've never personally run into any problems with $@ getting munged.

Cuenca answered 29/1, 2010 at 21:6 Comment(8)
Maybe people who like other programming languages make more mistakes.Ginzburg
Oh crap, did I just say that out loud? Just kidding!Ginzburg
Well, other languages have exceptions and Perl doesn't. That's the difference. That we fake them doesn't make Perl really have exceptions.Wiliness
@brian, OK, so what makes whatever Perl has not exceptions? IOW, what is the definition of an exception and why does eval/die not meet it?Cuenca
You figure out how you want to define exceptions, then we can talk about it. Let's not conflate things built on top of features with actual core features though.Wiliness
@brian: Succint and correct, of course, but I think what friedo is getting at is: what are the specific (negative) consequences of having an an-hoc "exception" mechanism rather than having it built into core as it is in other languages, and how severe are the problems thus created?Pushcart
Related: #2440466 and #503689Silvers
Quoting the OP who said: "The main problem I see with Perl's exception system is the use of the special global $@ to hold the current error", it is worth mentioning there are a few modules that implements exception variable assignment such as try{ # something } catch( $e ) { # do something with error $e }. There was TryCatch, but it got broken with version 0.006020 of Devel::Declare and now there is Nice::Try that I developed.Bannock
S
13

Some exception classes, e.g. Error, cannot handle flow control from within try/catch blocks. This leads to subtle errors:

use strict; use warnings;
use Error qw(:try);

foreach my $blah (@somelist)
{
    try
    {
        somemethod($blah);
    }
    catch Error with
    {
        my $exception = shift;
        warn "error while processing $blah: " . $exception->stacktrace();
        next;    # bzzt, this will not do what you want it to!!!
    };

    # do more stuff...
}

The workaround is to use a state variable and check that outside the try/catch block, which to me looks horribly like stinky n00b code.

Two other "gotchas" in Error (both of which have caused me grief as they are horrible to debug if you haven't run into this before):

use strict; use warnings;
try
{
    # do something
}
catch Error with
{
    # handle the exception
}

Looks sensible, right? This code compiles, but leads to bizarre and unpredictable errors. The problems are:

  1. use Error qw(:try) was omitted, so the try {}... block will be misparsed (you may or may not see a warning, depending on the rest of your code)
  2. missing semicolon after the catch block! Unintuitive as control blocks do not use semicolons, but in fact try is a prototyped method call.

Oh yeah, that also reminds me that because try, catch etc are method calls, that means that the call stack within those blocks will not be what you expect. (There's actually two extra stack levels because of an internal call inside Error.pm.) Consequently, I have a few modules full of boilerplate code like this, which just adds clutter:

my $errorString;
try
{
    $x->do_something();
    if ($x->failure())
    {
        $errorString = 'some diagnostic string';
        return;     # break out of try block
    }

    do_more_stuff();
}
catch Error with
{
    my $exception = shift;
    $errorString = $exception->text();
}
finally
{
    local $Carp::CarpLevel += 2;
    croak "Could not perform action blah on " . $x->name() . ": " . $errorString if $errorString;
};
Silvers answered 4/2, 2010 at 19:34 Comment(2)
All excellent points. Especially the flow-control item. I hadn't considered that one before.Cuenca
To the downvoter: I would welcome suggestions for improving this ugly code, it offends me as well. I have switched to TryCatch in subsequent projects, but this post was intended to demonstrate the pitfalls with a particular exception class (and it may extend to others; I haven't tried them all).Silvers
C
25

The typical method most people have learned to handle exceptions is vulnerable to missing trapped exceptions:

eval { some code here };
if( $@ ) {  handle exception here };

You can do:

eval { some code here; 1 } or do { handle exception here };

This protects from missing the exception due to $@ being clobbered, but it is still vulnerable to losing the value of $@.

To be sure you don't clobber an exception, when you do your eval, you have to localize $@;

eval { local $@; some code here; 1 } or do { handle exception here };

This is all subtle breakage, and prevention requires a lot of esoteric boilerplate.

In most cases this isn't a problem. But I have been burned by exception eating object destructors in real code. Debugging the issue was awful.

The situation is clearly bad. Look at all the modules on CPAN built provide decent exception handling.

Overwhelming responses in favor of Try::Tiny combined with the fact that Try::Tiny is not "too clever by half", have convinced me to try it out. Things like TryCatch and Exception::Class::TryCatch, Error, and on and on are too complex for me to trust. Try::Tiny is a step in the right direction, but I still don't have a lightweight exception class to use.

Congregationalism answered 30/1, 2010 at 0:8 Comment(0)
A
25

Try::Tiny (or modules built on top of it) is the only correct way to deal with exceptions in Perl 5. The issues involved are subtle, but the linked article explains them in detail.

Here's how to use it:

use Try::Tiny;

try {
    my $code = 'goes here';
    succeed() or die 'with an error';
}
catch {
    say "OH NOES, YOUR PROGRAM HAZ ERROR: $_";
};

eval and $@ are moving parts you don't need to concern yourself with.

Some people think this is a kludge, but having read the implementations of other languages (as well as Perl 5), it's no different than any other. There is just the $@ moving part that you can get your hand caught in... but as with other pieces of machinery with exposed moving parts... if you don't touch it, it won't rip off your fingers. So use Try::Tiny and keep your typing speed up ;)

Ashy answered 30/1, 2010 at 5:28 Comment(1)
Note that versions of Perl >= 5.14 now try to keep exceptions sane, so this is no longer the "only correct way". But it's still pretty good.Ashy
S
13

Some exception classes, e.g. Error, cannot handle flow control from within try/catch blocks. This leads to subtle errors:

use strict; use warnings;
use Error qw(:try);

foreach my $blah (@somelist)
{
    try
    {
        somemethod($blah);
    }
    catch Error with
    {
        my $exception = shift;
        warn "error while processing $blah: " . $exception->stacktrace();
        next;    # bzzt, this will not do what you want it to!!!
    };

    # do more stuff...
}

The workaround is to use a state variable and check that outside the try/catch block, which to me looks horribly like stinky n00b code.

Two other "gotchas" in Error (both of which have caused me grief as they are horrible to debug if you haven't run into this before):

use strict; use warnings;
try
{
    # do something
}
catch Error with
{
    # handle the exception
}

Looks sensible, right? This code compiles, but leads to bizarre and unpredictable errors. The problems are:

  1. use Error qw(:try) was omitted, so the try {}... block will be misparsed (you may or may not see a warning, depending on the rest of your code)
  2. missing semicolon after the catch block! Unintuitive as control blocks do not use semicolons, but in fact try is a prototyped method call.

Oh yeah, that also reminds me that because try, catch etc are method calls, that means that the call stack within those blocks will not be what you expect. (There's actually two extra stack levels because of an internal call inside Error.pm.) Consequently, I have a few modules full of boilerplate code like this, which just adds clutter:

my $errorString;
try
{
    $x->do_something();
    if ($x->failure())
    {
        $errorString = 'some diagnostic string';
        return;     # break out of try block
    }

    do_more_stuff();
}
catch Error with
{
    my $exception = shift;
    $errorString = $exception->text();
}
finally
{
    local $Carp::CarpLevel += 2;
    croak "Could not perform action blah on " . $x->name() . ": " . $errorString if $errorString;
};
Silvers answered 4/2, 2010 at 19:34 Comment(2)
All excellent points. Especially the flow-control item. I hadn't considered that one before.Cuenca
To the downvoter: I would welcome suggestions for improving this ugly code, it offends me as well. I have switched to TryCatch in subsequent projects, but this post was intended to demonstrate the pitfalls with a particular exception class (and it may extend to others; I haven't tried them all).Silvers
P
9

A problem I recently encountered with the eval exception mechanism has to do with the $SIG{__DIE__} handler. I had -- wrongly -- assumed that this handler only gets called when the Perl interpreter is exited through die() and wanted to use this handler for logging fatal events. It then turned out that I was logging exceptions in library code as fatal errors which clearly was wrong.

The solution was to check for the state of the $^S or $EXCEPTIONS_BEING_CAUGHT variable:

use English;
$SIG{__DIE__} = sub {
    if (!$EXCEPTION_BEING_CAUGHT) {
        # fatal logging code here
    }
};

The problem I see here is that the __DIE__ handler is used in two similar but different situations. That $^S variable very much looks like a late add-on to me. I don't know if this is really the case, though.

Pharynx answered 30/1, 2010 at 15:46 Comment(1)
If the library code is handling an exception using an eval, your DIE signal trap will get called -- even if the library code catches and ignores the exception. So it's a good idea for your SIG DIE handler to bow out and not treat it as a real die signal, as you show. You can also just return at the beginning of your signal trap sub if $^S is true. Perlvar describes the state of $^S in various situations. Note that you need use English; to use the descriptive variable instead of the cryptic $^S.Thirlage
R
2

With Perl, language and user-written exceptions are combined: both set $@. In other languages language exceptions are separate from user-written exceptions and create a completely separate flow.

You can catch the base of user written exceptions.

If there is My::Exception::one and My::Exception::two

if ($@ and $@->isa('My::Exception'))

will catch both.

Remember to catch any non-user exceptions with an else.

elsif ($@)
    {
    print "Other Error $@\n";
    exit;
    }

It's also nice to wrap the exception in a sub call the sub to throw it.

Rapping answered 29/1, 2010 at 22:38 Comment(1)
Hi, you seem new here. Your response doesn't seem to be an answer to the OP (what's broken about Perl exceptions), but a comment about someone else's answer (possibly Dave Kirby's)? As such, although the information you present is useful in general, since it is not an answer to the OP, so it should have been posted as a comment or elsewhere.Pushcart
M
1

In C++ and C#, you can define types that can be thrown, with separate catch blocks that manage each type. Perl type systems have certain niggling issues related to RTTI and inheritance, according from what I read on chomatic's blog.

I'm not sure how other dynamic languages manage exceptions; both C++ and C# are static languages and that bears with it a certain power in the type system.

The philosophical problem is that Perl 5 exceptions are bolted on; they aren't built from the start of the language design as something integral to how Perl is written.

Myles answered 29/1, 2010 at 21:11 Comment(1)
Type constraints are not types. Perl does not have any issues with types, because it doesn't have any types.Ashy
T
1

It has been a looong time since I used Perl, so my memory may be fuzzy and/or Perl may have improved, but from what I recall (in comparison with Python, which I use on a daily basis):

  1. since exceptions are a late addition, they are not consistently supported in the core libraries

    (Not true; they are not consistently supported in core libraries because the programmers that wrote those libraries don't like exceptions.)

  2. there is no predefined hierarchy of exceptions - you can't catch a related group of exceptions by catching the base class

  3. there is no equivalent of try:... finally:... to define code that will be called regardless of whether an exception was raised or not, e.g. to free up resources.

    (finally in Perl is largely unnecessary -- objects' destructors run immediately after scope exit; not whenever there happens to be memory pressure. So you can actually deallocate any non-memory resources in your destructor, and it will work sanely.)

  4. (as far as I can tell) you can only throw strings - you can't throw objects that have additional information

    (Completely false. die $object works just as well as die $string.)

  5. you cant get a stack trace showing you where the exception was thrown - in python you get detailed information including the source code for each line in the call stack

    (False. perl -MCarp::Always and enjoy.)

  6. it is a butt-ugly kludge.

    (Subjective. It's implemented the same way in Perl as it is everywhere else. It just uses differently-named keywords.)

Tenure answered 29/1, 2010 at 21:34 Comment(6)
#5 is not true - see Carp::cluck() and Carp::confess(), and it's not too much trouble to set a $SIG{__DIE__} and $SIG{__WARN__} handler with Carp to get stack traces by default. But #7 is extra true, so it evens out.Ginzburg
Re: #4, you can throw objects since Perl 5.6 at least (most exception systems on CPAN are based on this). I agree that finally semantics can get hairyCuenca
#3 - just put this code after eval, or use something like Error.pm or Try::Tiny.Zacatecas
If you include the main exception handling modules on CPAN (Error::Simple, Try::Tiny, Try::Catch), none of these are true.Silvers
@Ether: Are you sure? I'm having trouble finding Exception::NotButtUglyKludge on CPAN.Pushcart
What was previously #6 was completely false. Exceptions are thread safe. Global variables are not shared in Perl. (And if you use Coro instead, which is a good idea, it manages the globals on a per-coroutine basis.)Ashy
R
0

Don't use Exceptions for regular errors. Only Fatal problems that will stop the current execution should die. All other should be handled without die.

Example: Parameter validation of called sub: Don't die at the first problem. Check all other parameters and then decide to stop by returning something or warn and correct the faulty parameters and proceed. That do in test or development mode. But possibly die in production mode. Let the application decide this.

JPR (my CPAN login)

Greetings from Sögel, Germany

Riches answered 1/1, 2015 at 18:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.