What are the common pitfalls associated with Perl's eval
, which might make you choose to use a module such as Try::Tiny
?
eval in all its forms is used to execute a little Perl program, trapping any errors encountered so they don't crash the calling program.
This allows you to use a separate, perhaps user-supplied, piece of Perl script within your program. An eval EXPR statement is evaluated separately each time the function is called. The second form evaluates BLOCK when the rest of the script is parsed (before execution).
The eval block is parsed with the rest of the code and its return value is a reference to the array @a . That reference is assigned to $ttt . While the eval block goes out of scope, @a still has a non-zero reference count (thanks to $ttt ) so it still exists.
The eval function takes a character string, and evaluates it in the current perl environment, as if the character string were a line in the currently executing perl program.
Perl's eval
comes in two flavors, string eval and block eval. String eval invokes the compiler to execute source code. Block eval surrounds already compiled code in a wrapper that will catch the die
exception. (string eval catches the die
exception also, as well as any compilation errors).
Try::Tiny only applies to the block form of eval, but the following applies to both forms.
Every time you call eval
it will change the value of $@
. It will either be ''
if the eval succeeded or the error caught by the eval.
This means that any time you call an eval, you will clear any previous error messages. Try::Tiny
localizes the $@
variable for you, so that a successful eval will not clear the message of a previous failed eval.
The other pitfall comes from using $@
as the check to determine if the eval succeeded. A common pattern is:
eval {...};
if ($@) {
# deal with error here
}
This relies on two assumptions, first that any error message $@
could contain is a true value (usually true), and that there is no code between the eval block and the if statement.
Visually of course the latter is true, but if the eval block created an object, and that object went out of scope after the eval failed, then the object's DESTROY
method will be called before the if
statement. If DESTROY
happens to call eval without localizing $@
and it succeeds, then by the time your if
statement is run, the $@
variable will be cleared.
The solution to these problems is:
my $return = do {
local $@;
my $ret;
eval {$ret = this_could_fail(); 1} or die "eval failed: $@";
$ret
};
breaking that apart line by line, the local $@
creates a new $@
for the do
block which prevents clobbering previous values. my $ret
will be the return value of the evaluated code. In the eval block, $ret
is assigned to, and then the block returns 1
. That way, no matter what, if the eval succeeds it will return true, and if it fails it will return false. It is up to you what to do in the case of failure. The code above just dies, but you could easily use the return value of the eval block to decide to run other code.
Since the above incantation is a bit tedious, it becomes error prone. Using a module like Try::Tiny
insulates you from those potential errors, at the cost of a few more function calls per eval. It is important to know how to use eval properly, because Try::Tiny
is not going to help you if you have to use a string eval.
The issues are explained in the Try::Tiny documentation. Briefly, they are:
$@
$@
silently masks errors$@
might not be a true value If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With