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 forma 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 (bacuse 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.