In this form, eval is followed by a block of code, not a scalar containing a string. It is used for handling run-time errors, or exceptions. Errors can be internal built-in ones (out-of-memory, divide-by-zero) or user-defined ones produced by die.
The following example shows how you can use the block form eval to trap a run-time divide-by-zero error:
eval { $a = 10; $b = 0; $c = $a / $b; # Causes a run-time error, # which is trapped by eval }; print $@; # Prints "Illegal division by 0 at try.pl line 3
When the script is compiled, Perl syntax-checks the block of code and generates code. If it encounters a run-time error, Perl skips the rest of the eval block and sets $@ to the corresponding error text.
To signal your own errors, you use die. Perl knows whether a piece of code is currently executing inside an eval, and so, when die is called, Perl simply gives the error string - die's argument - to the global $@, and jumps to the statement following the eval block. In the following example, open_file invokes die if it has trouble opening a file. To use this function, wrap it inside an eval.
sub open_file { open (F, $_[0]) || die "Could not open file: $!"; } $f = 'test.dat'; while (1) { eval { open_file($f); # if open_file dies, the program doesn't quit }; last unless $@; # no error: break out of the loop. print "$f is not present. Please enter new file name $f"; chomp($f = <STDIN>); }
Java/C++ programmers would of course recognize the parallel to the throw, try, and catch statements, where try corresponds to the eval block, catch to the checking of $@, and throw to die. (Essentially, the caller says to the run-time environment, "Here, try this code, and catch whatever errors are thrown by the callee.")
One thing I like a lot about the Java environment is that both the interpreter and the standard libraries make extensive and consistent use of try, throw, and catch for error handling. In some ways, using these constructs is better than simply returning an error code, because it requires the programmer to pay attention to errors (if you ignore the error, the program dies).
In C++ and Java, a function can rethrow an exception if it doesn't want to handle it itself. In Perl, you can do so by calling die without arguments:
eval { ... }; if ($@ =~ /sorry, bucko/) { .... } else { # hmm .. don't know what to do with it. die; # Identical to die $@ }
If there is an enclosing eval block, this exception will be caught; otherwise, the program terminates.
Since C++ and Java contain special constructs for trapping and handling errors, some Perl programmers would like them too. Here are a couple of options.
As this book goes to press, a new module, Exception, built over eval and die, is just being announced to CPAN. You need to understand Perl's support for object orientation to understand the following small example, so you might want to revisit this example on a subsequent reading.
This snippet throws exceptions if you attempt to withdraw more than $300 or exceed the current balance:
use Exception; package AmountExceededException; # User-defined exception @ISA = ('Exception'); package OverdraftException; # User-defined exception @ISA = ('Exception'); package BankAccount; sub withdraw_money { my $amount = shift; if ($amount > 300) { throw new AmountExceededException; } if ($amount > $balance) { throw new OverdraftException; } ... # Change balance } try { print "How much do you need?"; chomp($amount = <STDIN>); withdraw_money ($amount); } catch AmountExceededException => sub {print 'Cannot withdraw more than $300'}, OverdraftException => sub {print $_[0]->message}, Default => sub {print "Internal error. Try later"};
The standard Perl library currently has a module called exceptions.pl, which is also a thin wrapper over eval and die and provides subroutines called catch and throw. catch takes a piece of code as a string (instead of as a block, as the previous example) and a list of regular expressions to match against the error string when it eval's the code.
This module has one serious problem, which is actually solved by the newer module, Exception.pm: because catch
is a subroutine, lexical variables in the current scope (localized with my) are not available to it.
I suspect that programmers disdain making a language look like another;[3] in the final analysis, using eval and die in the raw is probably the easiest option.
[3] That excludes Larry Wall, considering that he designed Perl to look like C, sh, and awk!