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. AbigailThread Previous | Thread Next