develooper Front page | perl.perl5.porters | Postings from March 2008

Re: local $@ has an unwanted side effect

Thread Previous | Thread Next
From:
Abigail
Date:
March 21, 2008 09:27
Subject:
Re: local $@ has an unwanted side effect
Message ID:
20080321162704.GA7226@abigail.be
On Fri, Mar 21, 2008 at 05:44:33PM +0200, Yuval Kogman wrote:
> 	sub foo {
> 		local $@;
> 
> 		eval { # a generic wrapper, doesn'tknow about bar()'s details
> 			 bar();
> 		};
> 
> 		if ( $@ ) {
> 			# do something meaningful
> 			die $@;
> 		}
> 	}
> 
> 	sub bar {
> 		die "blah";
> 	}
> 
> 	eval { foo() };
> 
> 	warn "Error: $@";
> 
> in the 'foo' subroutine $@ is localized to prevent clobbering it in
> cases such as:
> 
> 	eval { ... };
> 	foo();
> 	if ( $@ ) { # for the prev eval }
> 
> From a control flow POV everything works correctly here, but in the
> outermost eval { } the value of $@ is not preserved (it will jump
> though). The value is just ''.

Well, that's what you want, isn't? In:

    eval { ... }
    foo ();
    if ($@) { ... }

you want $@ to be the result of the first eval {} (at least, that's what 
I understand from your comment). How else do you want to achieve that then
by "ignoring" whatever foo() does with $@?

> Since there is no other way to know if the eval actually failed
> without inspecting $@ that makes it faily useless, and furthermore
> the documentation of eval implies this should not be the case (but
> doesn't mention local).


Inspecting $@ to check whether an eval die()d is wrong. It can
trigger both false positives, and false negatives:

    sub Foo::DESTROY {die "Hello"}
    sub Bar::DESTROY {eval ""}

    #
    # No die() here.
    #
    eval {my $o = bless [] => 'Foo';};
    if ($@) {warn "Triggered wrongly; the previous eval did NOT die.\n"}

    #
    # There's a die() here.
    #
    eval {my $o = bless [] => 'Bar'; die "Eeep"};
    unless ($@) {warn "Triggered wrongly; the previous eval DID die.\n"}

    __END__
    Triggered wrongly; the previous eval did NOT die.
    Triggered wrongly; the previous eval DID die.


The correct way of checking whether an eval failed is to check its return
value:

    sub Foo::DESTROY {die "Hello"}
    sub Bar::DESTROY {eval ""}

    #
    # No die() here.
    #
    eval {my $o = bless [] => 'Foo'; 1} or do {
        warn "Triggered wrongly; the previous eval did NOT die.\n"
    };

    #
    # There's a die() here.
    #
    eval {my $o = bless [] => 'Bar'; die "Eeep"; 1} and do {
        warn "Triggered wrongly; the previous eval DID die.\n"
    };

    __END__


> I believe this is an implementation detail, likely die() in the
> context of an eval assigning to $@ with it's localization stack,
> instead of the assignment happenning in the scope of the eval { }
> that is actually trapping the error, so in effect the error that it
> trapped is in $@.
> 
> The work around is in foo():
> 
> 	sub foo {
> 		my $e;
> 		{
> 			local $@;
> 			eval { bar () };
> 			$e = $@;
> 		}
> 
> 		if ( $e ) { die $e }
> 	}
> 
> but that kinda sucks.

But that's how it ought to be done.

While I agree that people might get bitten by 'local $@' (I have myself),
it *is* consistent. The eval fails (due to die), which sets $@. Then eval
is done, after which the scope is exited, triggering DESTROYs, and unrolling
the effects of local. IMO, you are calling for an exception.

> Every time this happenned to me it took a really long while to
> figure out, a conservative guess is that I've lost about 2-3 days of
> my life to this behavior over the past few years. This is because
> it's action at a distance on several levels.

But that's what local *IS* all about.

If you don't want the effects of local(), by all means, don't use it.
Don't make an exception for a specific case. Specially not for a case
where you were using it wrongly in the first place.



Abigail

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About