I started out noticing something odd about Scope::Guard
.
$guard
variable as the very last statement in a sub, the guard's sub gets
called later than I expect. undef $guard
, it gets called when the reference goes out of
scope as documented. I wonder why.The code is also to be found here
my $sClass = 'SGuard';
# Uncomment to use Scope::Guard instead:
# use Scope::Guard; $sClass = 'Scope::Guard';
package SGuard;
sub new {
my ($class, $sub) = @_;
return bless { sub => $sub }, $class;
}
sub DESTROY {
my ($self) = @_;
$self->{sub}->();
}
package main;
sub mySub {
my $mySubGuard = $sClass->new(sub {
print "shouldDestroyFirst\n"
});
# Do something - any no-op will do.
undef;
# Comment out this line and it works
undef $mySubGuard;
# Or uncomment the next undef line and it works. Any statement(s) or
# no-ops will do but we test the return value of mySub to make sure it
# doesn't return a reference, so undef...
# undef;
}
{
my $scopeGuard = $sClass->new(sub {
print "shouldDestroyLast\n"
});
# Check that mySub returns undef to ensure the reference *did* go out
# of scope
printf "mySub returned a %sdefined value\n", defined mySub() ? "" : "un";
}
print "done\n";
In the code I've made my own poor-man's Scope::Guard
(SGuard
above)
just to make the example as simple as possible. You can also use Scope::Guard
and get the exact same results that are unexpected at least to me.
I'm expecting that the $mySubGuard
inside mySub()
should be destroyed first
and the $scopeGuard
in the scope that calls mySub()
should be destroyed
last. And so get output like:
shouldDestroyFirst
mySub returned a undefined value
shouldDestroyLast
done
I get above output if I use undef $mySubGuard
line in mySub.
If I don't use undef $mySubGuard
line in mySub, I get below output:
mySub returned a undefined value
shouldDestroyLast
shouldDestroyFirst
done
So, it looks like the $mySubGuard
from mySub()
is destroyed
after variables local to the outer scope are destroyed.
Why does behavior differ just because I undef a variable that is about to go out of scope anyway? And why does it matter whether something is done afterwards?
It looks like it's some sort of optimization. If you undef
a variable and don't use it afterward, it gets put in some sort of queue to check magic or something. But if you do anything afterwards, it does the DESTROY right then.
Also, there could be a bug that since you are undef
-ing it in say a "return context", there is a copy of the variable that's being inspected for something. And perhaps Perl retains a reference which it later cleans up and the end of the calling scope.
You'll also notice that any other statement after undef
-ing the guard results in expected behavior. Including the PBP-recommended ending all subs with return
. One of Damian's explicit reasons was it avoids unexpected side-effects.
Problem solved: as easily as undef
-ing a guard, you can run its handler straight. Don't undef guards as the last (implicit return) statement of a sub. Have a reason to explicitly undef guards, such as you want to run their handler right then, to do further processing.
It's confusing and unexpected, but definitely not something that should arise in finished or standardized code.
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