What are the common pitfalls when using Perl's eval?

后端 未结 4 745
天涯浪人
天涯浪人 2021-02-04 06:54

What are the common pitfalls associated with Perl\'s eval, which might make you choose to use a module such as Try::Tiny?

4条回答
  •  北荒
    北荒 (楼主)
    2021-02-04 07:02

    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.

提交回复
热议问题