Exception handling in Perl: How to deal with fatal errors in external modules
Originally the accepted way in Perl to signal an error was to return undef and let the users of the function or module decide what they want to do with it. If they even look at it.
In other languages throwing an exception is the standard way to signal an error.
Some of the Perl modules on CPAN follow the "return undef" path, others will throw an exception. The question how to deal with that?
Parse dates
In this example we use the Time::Piece module to parse strings represent dates. The strptime method accept a string representing a date and a format string using strptime formatting instructions. It then tries to parse the string according to the formatting instructions. In our example the formatting instructions "%d %b, %Y" mean we are expecting to have a number representing the days, followed by a string representing the month (e.g. 'Nov'), and then a 4-digit number representing the year.
examples/time_piece.pl
use 5.010; use strict; use warnings; use Time::Piece; my @dates = ("3 Nov, 1989", "Nov, 1989", "1 Jan, 1999"); foreach my $d (@dates) { my $tp = Time::Piece->strptime($d, "%d %b, %Y"); say $tp; }
If we run this code we'll see the following output:
$ perl examples/time_piece.pl Fri Nov 3 00:00:00 1989 Error parsing time at .../Time/Piece.pm line 469.
What happened here is that we tried to parse 3 strings, but the 2nd string was incorrectly formatted. The strptime method raised an exception to indicate this failure (probably by calling die) that caused our script to die.
Catch the exception using eval
While there are other ways to handle exceptions using, for example Try::Tiny, in this case we look at the built-in eval statement.
If we wrap a piece of code in an eval block, the eval will capture any exception that might happen within that block, and it will assign the error message to the $@ variable of Perl.
The simple way to do this looks like this:
(please note, there is a ; after the eval block.)
eval { # code that might throw exception }; if ($@) { # report the exception and do something about it }
A more robust way to write this looks like this:
eval { # code that might throw exception 1; # always return true to indicate success } or do { # this block executes if eval did not return true (because of an exception) # save the exception and use 'Unknown failure' if the content of $@ was already # removed my $error = $@ || 'Unknown failure'; # report the exception and do something about it };
Here too, there is a trailing ;, but it is only after the do block.
Error handling for Time::Piece
We used the eval or do expression we have just seen. In this solution we have both the creation of $tp and its use inside the eval block. That's because if we cannot parse the date-string then there is no point in trying to us that variable.
examples/time_piece_eval.pl
use 5.010; use strict; use warnings; use Time::Piece; my @dates = ("3 Nov, 1989", "Nov, 1989", "1 Jan, 1999"); foreach my $d (@dates) { eval { my $tp = Time::Piece->strptime($d, "%d %b, %Y"); say $tp; 1; } or do { my $error = $@ || 'Unknown failure'; warn "Could not parse '$d' - $error"; }; }
If we run this script we'll get the following:
Fri Nov 3 00:00:00 1989 Could not parse 'Nov, 1989' - Error parsing time at .../Time/Piece.pm line 469. Fri Jan 1 00:00:00 1999
This means, that although the second string could not parse and Time::Piece raised an exception, we captured that exception, reported it and then let the foreach loop go on to the third value that was properly parsed and printed.
Comments
That's cool, but can you also capture undef somehow in an eval or do block?
--- Nope. If some function returns undef on failure then you need to either check that manually or convert it to an exception with the "f() or die" construct that you probably have seen in the "open ... or die" case.
I'm not a fan of functions that return undef rather than just using a bare return, for the reasons explained in Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef https://metacpan.org/pod/Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef
The second structure of catching an error is described as 'more robust'. In what way is it superior to the first method?
--- In my opinion the second approach is more robust in the sense that it's a whole code block that takes care of everything you cared about (execution and exception handling). Whereas the first approach separates the eval and the action upon a detected exception in an independent code block, hence possibly leading to dismember the two things, for example, when doing some maintenance or refactoring. It's really dependent on what you want to handle the execution and the mitigation: separatedly or together.
--- Spot on, Carlaanga. When you separate your eval from your exception handling, as is the case in the first example, and if, as you said, the eval and the exception handler get separated, it's possible for $@ to get "clobbered", which is to say overwritten by another eval block somewhere. That's why I almost always use the "more robust" construction.
There are also a number of try-catch implementations in perl.
One that implements all the features you would expect is Nice::Try, such as:
try { # Something worth dying } catch( Exception $e ) { # catch exception of class Exception } catch( $e ) { return( "I failed: $e" ); } finally { # Do some mop up }
Published on 2017-04-16