Several things may go wrong in any program, including problems in programming, bad or missing input, unreachable external resources, and many other things. Perl doesn’t have any built-in error handling. It knows when it couldn’t do something, and it can tell me about errors, but it’s up to me as the Perl programmer to ensure that my program does the right thing, and when it can’t, try to do something useful about it.
Perl has four special variables it uses to report errors: $!
, $?
, $@
, and
$^E
. Each reports different sorts of
errors. Table 12-1 shows the four variables and
their descriptions, which are also in perlvar.
The simplest errors occur when Perl asks the system to do
something, but the system can’t or doesn’t do it for some reason. In
most cases the Perl built-in returns false and sets $!
with the error message. If I try to read a
file that isn’t there, open
returns
false and puts the reason it failed in $!
:
open my( $fh ), '<', 'does_not_exist.txt' or die "Couldn't open file! $!";
The Perl interpreter is a C program, and it does its work through the library of C
functions it’s built upon. The value of $!
represents the result of the call to the
underlying C function, which comes from the errno.h header file. That’s the one from the
standard C library. Other applications might have a file of the same
name. The errno.h file associates
symbolic constants with each error value and gives a text description
for them. Here’s an excerpt from the errno.h from Mac OS X:
#define EPERM 1 /* Operation not permitted */ #define ENOENT 2 /* No such file or directory */ #define ESRCH 3 /* No such process */
In my open
example, I
interpolated $!
in a string and got a
human-readable error message out of it. The variable, however, has a
dual life. Scalars that have different string and numeric values are
known as dualvars.[46]The numeric value is the errno
value from the C function, and the
string value is a human-readable message. By setting $!
myself I can see both values. I
use printf
’s format
specifiers to force both the numeric and string versions of the same
scalar:
for ($! = 0; $! <= 102; $!++) { printf("%d: %s\n", $!, $! ); }
The output shows the numeric value as well as the string value:
1: Operation not permitted 2: No such file or directory 3: No such process ...
The value of $!
is only
reliable immediately after the library call. I should only use $!
immediately after the expression I want to
check. My next Perl statement might make another library call, which
could again change its value, but with a different message. Also, a
failed library call sets the value, but a successful one doesn’t do
anything to it and won’t reset $!
. If
I don’t check the value of $!
right
away, I might associate it with the wrong statement.
That’s not the whole story, though. The %!
hash has some magic to go along with
$!
. The keys to %!
are the symbolic constants, such as
ENOENT
, from errno.h. This is a magic hash so only the key
that corresponds to the current $!
has a value. For instance, when Perl can’t open my does_not_exist.txt, it sets $!
with the value represented by ENOENT
. At the same time Perl sets the value
of $!{ENOENT}
. No other keys in
%!
will have a value. This means I
can check what happened when I try to recover from the failed open
by taking appropriate action based on the
type of error.
If Perl sees %!
anywhere in the
program, it automatically loads the Errno
module,
which provides functions with the same name as the
errno.h symbolic constants so I can
get the number for any error. I don’t have to use %!
to get this, though. I can load it myself,
and even import the symbols I want to use:
use Errno qw(ENOENT); print "ENOENT has the number " . ENOENT . "\n";
In this example program, I want to write some information to disk.
It’s very important information, so I want to take extra care to ensure
I save it. I can’t simply die
and
hope somebody notices. Indeed, if I can’t write to the file because the
disk is full, my warning may never even make it to a logfile:
#!/usr/bin/perl use File::Spec; my $file = 'does_not_exist.txt'; my $dir = 'some_dir'; my $fh; my $try = 0; OPEN: { last if $try++ >= 2; my $path = File::Spec->catfile( $dir, $file ); last if open $fh, '>', $path; warn "Could not open file: $!...\n"; if( $!{ENOENT} ) # File doesn't exist { # Ensure the directory is there warn "\tTrying to make directory $dir...\n"; mkdir $dir, 0755; } elsif( $!{ENOSPC} ) # Full disk { # Try a different disk or mount point warn "\tDisk full, try another partition...\n"; $dir = File::Spec->catfile( File::Spec->rootdir, 'some_other_disk', 'some_other_dir' ); } elsif( $!{EACCES} ) # Permission denied { warn "\tNo permission! Trying to reset permissions...\n"; system( '/usr/local/bin/reset_perms' ); } else { # give up and email it directly... last; } redo; } print $fh "Something very important\n";
Though this is a bit of a toy example, I can see that I have a lot
of power to try to recover from a system error. I try to recover in one
of four ways, and I’ll keeping running the naked block I’ve labeled with
OPEN
until it works or I’ve tried
enough things (at some point it’s hopeless, so give up). If I can open
the filehandle, I break out of the naked block with last
. Otherwise, I look in %!
to see which key has a true value. Only one
key will hold a true value, and that one corresponds to the value in
$!
. If I get back an error saying the
file doesn’t exist, I’ll try to create the directory it’s going to so I
know it’s there. If there’s no space left on the disk, I’ll try another
disk. If I don’t have the right permissions, I’ll try to reset the
permissions on the file. This used to be a big problem at one of my
jobs. A lot of people had admin privileges and would do things that
inadvertently changed permissions on important files. I wrote a setuid
program that pulled the right permissions from a database and reset
them. I could run that from any program and try the open
again. That sure beats a phone call in
the middle of the night. Since then, I’ve realized the lack of wisdom in
letting just anyone be root.
To tell me what went wrong with subprocesses that my programs start,
Perl uses $?
to let me see the child process exit status. Perl can
communicate with external programs through a variety of mechanisms,
including:
system( ... ); `....`; open my($pipe), "| some_command"; exec( 'some command' ); my $pid = fork(); ...; wait( $pid );
If something goes wrong, I don’t see the error right away. To run
an external program, Perl first forks, or makes a copy of the current
process, then uses exec
to turn
itself into the command I wanted. Since I’m already running the Perl
process, it’s almost assured that I’ll be able to run another copy of it
unless I’ve hit a process limit or run out of memory. The first part,
the fork
, will work. There won’t be
any error in $!
because there is no C
library error. However, once that other process is up and running, it
doesn’t show its errors through the $!
in the parent process. It passes its exit
value back to the parent when it stops running, and Perl puts that in
the $?
. I won’t see that error until
I try to clean up after myself when I use close
or wait
:
close( $pipe ) or die "Child error: $?"; wait( $pid ) or die "Child error: $?";
The value of $?
is a bit more
complicated than the other error variables. It’s actually a word (two
bytes). The high byte is the exit status of the child process. I can
shift all the bits to the right eight places to get that number. This
number is specific to the program I run so I need to check its
documentation to assign the proper meaning:
my $exit_value = $? >> 8;
The lower seven bits of $?
hold
the signal number from which the process died if it died from a
signal:
my $signal = $? & 127; # or use 0b0111_1111
If the child process dumped core, the eighth bit in the low word is set:
my $core_dumped = $? & 128; # or use 0b1000_000;
When I use Perl’s exit
, the
number I give as an argument is the return value of the process.
That becomes the high word in $?
if
some other Perl program is the parent process. My exit-with-value.pl program exits with
different values:
#!/usr/bin/perl # exit-with-value.pl # exit with a random value exit time % 256;
I can call exit-with-value.pl
with another program, exit-with-value-call.pl. I call the first
program with system
, after which I
get the exit
value by shifting
$?
down eight positions:
#!/usr/bin/perl # exit-with-value-call.pl system( "perl exit-with-value.pl" ); my $rc = $? >> 8; print "exit value was $rc\n";
When I run my program, I see the different exit
values:
$ perl exit-with-value-call.pl exit value was 102 $ perl exit-with-value-call.pl exit value was 103 $ perl exit-with-value-call.pl exit value was 104
If I use die
instead of
exit
, Perl uses the value 255 as the
exit
value. I can change that by
using an END
block and assigning to $?
just before Perl is going to end the
program. When Perl enters the END
block right after a
die
, $?
has the value Perl intends to use as the
exit
value. If I see that is 255, I know I came from
a die
and can set the
exit
value to something more meaningful:
END { $? = 37 if $? == 255 }
On some systems, Perl might even be able to give me more
information about the error by using the $^E
variable. These errors typically come from outside Perl, so even
though Perl might not detect a problem using external libraries, the
operating system can set its own error variable.
As far as standard Perl is concerned, the value for $^E
is usually the same as $!
. For the things that the Perl language
does, I’m not going to get extra information in $^E
. On VMS, OS/2, Windows, or MacPerl, I might get extra information,
though.
That doesn’t mean that platform-specific modules can’t use
$^E
to pass back information. When
they talk to other libraries or resources, Perl isn’t necessarily going
to pick up on errors in those operations. If a library call returns a
result indicating failure, Perl might treat it as nothing special. The
calling module, however, might be able to interpret the return value,
determine it’s an error, and then set $^E
on its own.
The Mac::Carbon
module passes back error information from the Mac interface
through $^E
, and I can use
Mac::Errors
to translate that information into the number, symbolic
constant, or description for the error. The Mac::Glue
program I use to run RealPlayer on another machine I have hooked up to my home
stereo system looks at $^E
to figure
out what went wrong:
#!/usr/bin/perl # mac-realplayer.pl use Mac::Errors qw($MacError); use Mac::Glue; print "Trying machine $ENV{REALPLAYER_MACHINE}...\n"; my $realplayer = Mac::Glue->new( 'Realplayer', eppc => 'RealPlayer', $ENV{REALPLAYER_MACHINE}, undef, undef, map { $ENV{"REALPLAYER_MACHICE_$_"} } qw( USER PASS ) ); $realplayer->open_clip( with_url => $ARGV[0] ); if( $^E ) { my $number = $^E + 0; die "$number: $MacError\n"; }
Several things might go wrong in this program. I require several
environment variables and a command-line argument. If I forget to set
$ENV{REALPLAYER_MACHINE}
or specify a
URL on the command line, I get an error telling me something is
missing:
$ perl mac-realplayer.pl Trying machine ... -1715: a required parameter was not accessed
If I forget to set $ENV{REALPLAYER_MACHINE_USER}
or $ENV{REALPLAYER_MACHINE_PASS}
, Mac OS X
prompts me for a username and password to access the remote machine. If
I cancel that dialog, I get a different error showing that I didn’t go
through the authentication:
$ perl mac-realplayer.pl Trying machine realplayer.local... -128: userCanceledErr
For Windows, $^E
has whatever
Win32::GetLastError()
returns.
The Win32
family of modules uses
$^E
to pass back error information. I
can use Win32::FormatMessage()
to
turn the number into a descriptive string. The
Text::Template::Simple
module, for instance, tries to use the Win32
module to get a Windows path, and if it can’t do that, it uses GetLastError
:
package Text::Template::Simple; if(IS_WINDOWS) { require Win32; $wdir = Win32::GetFullPathName($self->{cache_dir}); if( Win32::GetLastError() ) { warn "[ FAIL ] Win32::GetFullPathName\n" if DEBUG; $wdir = ''; # croak "Win32::GetFullPathName: $^E"; } else { $wdir = '' unless -e $wdir && -d _; } }
On VMS, if $!{VMSERR}
is true,
I’ll find more information in $^E
.
Other operating systems may use this,
too.
So far I’ve shown how Perl tells me about errors, but what if I want to
tell the programmer about something that went wrong in one of my modules?
I have a few ways to do this. I’m going to use Andy Wardley’s Template
toolkit to show this
since it has all of the examples I need. Other modules might do it their
own way.
The simplest thing to do, and probably the one that annoys me the
most when I see it, is to set a package variable and let the user check
it. I might even set $!
myself. I mean,
I can do that, but don’t mistake that for an
endorsement. The Template
module sets the $Template::ERROR
variable when something goes
wrong:
my $tt = Template->new() || carp $Template::ERROR, "\n";
Package variables aren’t very nice, though. They are bad karma, and
programmers should avoid them when possible. In addition to, and much
better than, the package variable is the error
class method. Even if I don’t get an
object when I try to create one, I can still ask the
Template
class to give me the error:
my $tt = Template->new() || carp Template->error, "\n";
If I already have an object, I can use error
to find out what went wrong with the last
operation with that object. The error
method returns an error object from
Template::Exception
. I can inspect the type and
description of the error:
$tt->process( 'index.html' ); if( my $error = $tt->error ) { croak $error->type . ": " . $error->info; }
In this case, I don’t need to build the error message myself since
the as_string
method does it for
me:
$tt->process( 'index.html' ); if( my $error = $tt->error ) { croak $error->as_string; }
I don’t even need to call as_string
since the object will automatically
stringify itself:
$tt->process( 'index.html' ); if( my $error = $tt->error ) { croak $error; }
The main design at play in error handling in
Template
is that the return value of a function or
method does not report the error. The return value shouldn’t do more
than the function is supposed to do. I shouldn’t overload the return
value to be an error communicator too. Sure, I might return nothing when
something goes wrong, but even a false value has problems, since 0, the
empty string, or the empty list might be perfectly valid values from a
successful execution of the subroutine. That’s something I have to
consider in the design of my own systems.
Suppose I have a function named foo
that returns a string. If it doesn’t work,
it returns nothing. By not giving return
a value, the caller gets no value in
scalar or list context (which Perl will translate to undef
or the empty list):
sub foo { ... return unless $it_worked; ... return $string; }
That’s simple to document and understand. I certainly don’t want
to get into a mess of return values. Down that road lies madness and
code bloat as even the seemingly simple functions are overrun by code to
handle every possible error path. If foo
starts to return a different value for
everything that goes wrong, I dilute the interesting parts of foo
:
sub foo { ... return -1 if $this_error; return -2 if $that_error; ... return $string; }
Instead, I can store the error information so the programmer can
access it after she notices the call doesn’t work. I might just add an
error
slot to the instance or class
data. In Template
’s process
method, if anything goes wrong,
another part of the system handles and stores it. The process
method just returns the error:
# From Template.pm sub process { my ($self, $template, $vars, $outstream, @opts) = @_; ... if (defined $output) { ... return 1; } else { return $self->error($self->{ SERVICE }->error); } }
The error
method actually lives
in Template::Base
, and it does double duty as a
method to set and later access the error message. This function lives in
the base class because it services all of the modules in the Template
family. It’s actually quite slick in
its simplicity and utility:
# From Template/Base.pm sub error { my $self = shift; my $errvar; { no strict qw( refs ); $errvar = ref $self ? \$self->{ _ERROR } : \${"$self\::ERROR"}; } if (@_) { $$errvar = ref($_[0]) ? shift : join('', @_); return undef; } else { return $$errvar; } }
After getting the first argument, it sets up $errvar
. If $self
is a reference (i.e., called as $tt->error
), it must be an instance, so it
looks in $self->{_ERROR}
. If
$self
isn’t a reference, it must be a
class name (i.e., called as Template->error
), so it looks in the
package variable to get a reference to the error object. Notice that
Andy has to turn off symbolic reference checking there so he can
construct the full package specification for whichever class name is in
$self
, which can be any of the
Template
modules.
If there are additional arguments left in @_
, I must have asked error
to set the message so it does that and
returns undef
.[47]Back in process
, the
return value is just what the error
method returns. On the other hand if @_
is empty, it must mean that I’m trying to
get the error message, so it dereferences $errvar
and returns it. That’s what I get back
in $error
in my program.
That’s it. Although I may not want to do it exactly the way that Andy did, it’s the same basic idea: put the data somewhere else and give the programmer a way to find it. Return an undefined value to signal failure.
Perl doesn’t have exceptions. Let’s just get that clear right now. Like some other things Perl doesn’t really have, people have figured out how to fake them. If you’re used to languages, such as Java or Python, set the bar much lower so you aren’t too disappointed. In those other languages, exceptions are part of the fundamental design, and that’s how I’m supposed to deal with all errors. Exceptions aren’t part of Perl’s design, and it’s not how Perl programmers tend to deal with errors.
Although I’m not particularly fond of exceptions in Perl, there’s a decent argument in favor of them: the programmer has to handle the error, or the program stops.
Having said all that, though, I can fake rudimentary exceptions.
The easiest method uses a combination of die
and eval
. The die
throws the exception (meaning I have to do
it on my own), and the eval
catches
it so I can handle it. When eval
catches an error, it stops the block of code, and it puts the error
message into $@
. After the eval
, I check that variable to see if
something went wrong:
eval { ...; open my($fh), ">", $file or die "Could not open file! $!"; }; if( $@ ) { ...; # catch die message and handle it }
The eval
might even catch a
die
several levels away. This “action
at a distance” can be quite troubling, especially since there’s no way
to handle the error, then pick up where the code left off. That means I
should try to handle any exceptions as close to their origin as
possible.
If I use die
as an exception
mechanism, I can propagate its message through several layers of
eval
. If I don’t give die
a message, it uses the current value of
$@
:
#!/usr/bin/perl # chained-die.pl eval{ eval { eval { # start here open my($fh), ">", "/etc/passwd" or die "$!"; }; if( $@ ) { die; # first catch } }; if( $@ ) { die; # second catch } }; if( $@ ) { print "I got $@"; # finally }
When I get the error message I see the chain of propagations. The
original message Permission
denied
comes from the first die
, and each
succeeding die
tacks on a ...propagated
message until I finally get to
something that handles the error:
I got Permission denied at chained-die.pl line 8. ...propagated at chained-die.pl line 12. ...propagated at chained-die.pl line 17.
I might use this to try to handle errors, and failing that, pass
the error up to the next level. I modify my first error catch to append
some additional information to $@
,
although I still use die
without an
argument:
#!/usr/bin/perl # chained-die-more-info.pl eval{ eval { my $file = "/etc/passwd"; eval { # start here open my($fh), ">", $file or die "$!"; }; if( $@ ) { my $user = getpwuid( $< ); my $mode = ( stat $file )[2]; $@ .= sprintf "\t%s mode is %o\n", $file, $mode; $@ .= sprintf( "\t%s is not writable by %s\n", $file, $user ) unless -w $file; die; # first catch } }; if( $@ ) { die; # second catch } }; if( $@ ) { print "I got $@"; # finally }
I get the same output as I did before, but with my additions. The
subsequent die
s just take on their
...propagated
message:
I got Permission denied at chained-die-more-info.pl line 10. /etc/passwd mode is 100644 /etc/passwd is not writable by brian ...propagated at chained-die-more-info.pl line 19. ...propagated at chained-die-more-info.pl line 24.
Exceptions need to provide at least three things to be useful: the type
of error, where it came from, and the state of the program when the
error occurred. Since the eval
may be
far removed from the point where I threw an exception, I need plenty of
information to track down the problem. A string isn’t really good enough
for that.
I can give die
a reference
instead of a string. It doesn’t matter what sort or reference it is. If
I catch that die
within an eval
, the reference shows up in $@
. That means, then, that I can create an
exception class and pass around exception objects. When I inspect
$@
, it has all the object goodness I
need to pass around the error information.
In this short program I simply give die
an anonymous array. I use the
Perl compiler directives __LINE__
and __PACKAGE__
to insert the current line number and current package as
the values, and I make sure that __LINE__
shows up on the line that I want to
report (the one with die
on it). My
hash includes entries for the type of error and a text message, too.
When I look in $@
, I dereference it
just like a hash:
#!/usr/bin/perl # die-with-reference.pl eval { die { 'line' => __LINE__, 'package' => __PACKAGE__, 'type' => 'Demonstration', 'message' => 'See, it works!', }; }; if( $@ ) { print "Error type: $@->{type}\n" . "\t$@->{message}\n", "\tat $@->{package} at line $@->{line}\n"; }
This works with objects, too, since they are just blessed
references, but I have to make an important change. Once I have the
object in $@
, I need to save it to
another variable so I don’t lose it. I can call one method on $@
before Perl has a chance to reset its
value. It was fine as a simple reference because I didn’t do anything
that would change $@
. As an object,
I’m not sure what’s going on in the methods that might change it:
#!/usr/bin/perl # die-with-blessed-reference.pl use Hash::AsObject; use Data::Dumper; eval { my $error = Hash::AsObject->new( { 'line' => __LINE__ - 1, 'package' => __PACKAGE__, 'type' => 'Demonstration', 'message' => 'See, it works!', } ); die $error; }; if( $@ ) { my $error = $@; # save it! $@ might be reset later print "Error type: " . $error->type . "\n" . "\t" . $error->message . "\n", "\tat " . $error->package . " at line " . $error->line . "\n"; }
Since die
without an argument
propagates whatever is in $@
, it will do that if $@
holds a reference. This next program is
similar to my previous chained-die example, except that I’m storing the
information in an anonymous hash. This makes the error message easier to
use later because I can pull out just the parts I need when I want to
fix the problem. When I want to change $@
, I first get a deep copy of it (see Chapter 14) since anything I might call could reset $@
. I put my copy in $error
and use it in my die
. Once I have my reference, I don’t have to
parse a string to get the information I need:
#!/usr/bin/perl # chanined-die-reference.pl eval{ eval { my $file = "/etc/passwd"; eval { # start here open my($fh), ">", $file or die { errno => $! } }; if( $@ ) { use Storable qw(dclone); my $error = dclone( $@ ); @{ $error }{ qw( user file mode time ) } = ( scalar getpwuid( $< ), $file, (stat $file)[2], time, ); die $error; # first catch } }; if( $@ ) { die; # second catch } }; if( $@ ) { use Data::Dumper; print "I got " . Dumper($@) . "\n"; # finally }
This gets even better if my reference is an object because I can
handle the propagation myself. The special method named PROPAGATE
, if it exists, gets a chance to
affect $@
, and its return value
replaces the current value of $@
. I
modify my previous program to use my own very simple Local::Error
package to handle the
errors. In Local::Error
I skip the usual good module
programming practices to illustrate the process. In new
I simply bless the first argument into the
package and return it. In my first die
I use as the argument my
Local::Error
object. After that each die
without an argument uses the value of
$@
. Since $@
is an object, Perl calls its PROPAGATE
method, in which I add a new element
to $self->{chain}
to show the file
and line that passed on the error:
#!/usr/bin/perl # chained-die-propagate.pl use strict; use warnings; package Local::Error; sub new { bless $_[1], $_[0] } sub PROPAGATE { my( $self, $file, $line ) = @_; $self->{chain} = [] unless ref $self->{chain}; push @{ $self->{chain} }, [ $file, $line ]; $self; } package main; eval{ eval { my $file = "/etc/passwd"; eval { # start here unless( open my($fh), ">", $file ) { die Local::Error->new( { errno => $! } ); } }; if( $@ ) { die; # first catch } }; if( $@ ) { die; # second catch } else { print "Here I am!\n"; } }; if( $@ ) { use Data::Dumper; print "I got " . Dumper($@) . "\n"; # finally }
I just dump the output to show that I now have all of the information easily accessible within my object:
I got $VAR1 = bless( { 'chain' => [ [ 'chained-die-propagate.pl', 37 ], [ 'chained-die-propagate.pl', 42 ] ], 'errno' => 'Permission denied' }, 'Local::Error' );
My example has been very simple, but I can easily modify it to use a much more useful object to represent exceptions and specific sorts of errors.
The Fatal
module makes exceptions out of errors from Perl built-ins that
normally return false on failure. It uses some of the subroutine
wrapping magic I showed in Chapter 10. I don’t have to
check return values anymore because I’ll catch them as exceptions. I
have to specify in the Fatal
import list exactly
which functions should do this:
use Fatal qw(open); open my($fh), '>', $file;
Instead of silently failing, Fatal
causes the
program to automatically die when the open
fails. The message it produces is a
string, although not a particularly good-looking one:
Can't open(GLOB(0x1800664), <, does_not_exist): No such file or directory at (eval 1) line 3 main::__ANON__('GLOB(0x1800664)', '<', 'does_not_exist') called at /Users/brian/Dev/mastering_perl/trunk/Scripts/Errors/Fatals.pl line 5
To catch that, I wrap an eval
around my open
to catch the die
that Fatal
installs:
use Fatal qw(open); eval { open my($fh), '>', $file; } if( $@ ) { print "Could not open $file: $@"; }
I can also do it the more conventional way by specifying :void
in the import list. When I do that,
Fatal
only does its magic when I don’t use the return
value of the function. In the next snippet I use the return value in the
short-circuit or
so
Fatal
stays out of my way:
use Fatal qw(:void open); open my($fh), ">", $file or die "..."; # no exception eval { open my($fh), '>', $file; } if( $@ ) { print "Could not open $file: $@"; }
I can use Fatal
with any of the Perl built-ins
except for system
and exec
, but I have to list all of the functions
that I want to affect in the import list. Even if I don’t like
exceptions, this is a handy module to find the places where I have
unchecked calls to open
or any other
functions that should have a bit of supervision.
Perl has many ways to report something that goes wrong, and I have to know which one is appropriate for what am I doing. Besides the things that Perl can detect are errors from operating system libraries and other modules.
The perlfunc entries for die
and eval
explain more of the details.
Arun Udaya Shankar covers “Object Oriented Exception Handling in
Perl” for Perl.com: http://www.perl.com/pub/a/2002/11/14/exception.html.
He shows the Error
module, which provides an exception
syntax that looks more like Java than Perl with its try-catch-finally
blocks.
Get Mastering Perl now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.