Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Lexicals in if() scope gotcha!

by liz (Monsignor)
on Mar 30, 2004 at 23:32 UTC ( #341138=perlmeditation: print w/ replies, xml ) Need Help??

This came up on perl5-porters this week:

The question is, when does the object get destroyed given this code?

if (my $object = Foo->new) {} print "after if\n"; package Foo; sub new { my $self = bless {},shift; print "CREATED $self\n"; $self } sub DESTROY { print "DESTROYED $_[0]\n" }
Well, contrary to expectation (well, at least mine), the object does not get destroyed until global destruction! As the following output shows:
CREATED Foo=HASH(0xfc65c) after if DESTROYED Foo=HASH(0xfc65c)
instead of the expected:
CREATED Foo=HASH(0xfc65c) DESTROYED Foo=HASH(0xfc65c) after if
It appears that this behaviour is intentional and goes back until at least 5.00504. It is caused by the fact that any lexical inside an if() gets a refcount of 2, rather than of 1.

If this really is a problem for you, then there is the Internals module which allows you to set the refcount of a variable from Perl.

use Internals qw(SetRefCount); if (my $object = Foo->new) { SetRefCount( $object,1 ) } print "after if\n"; package Foo; sub new { my $self = bless {},shift; print "CREATED $self\n"; $self } sub DESTROY { print "DESTROYED $_[0]\n" } __END__ CREATED Foo=HASH(0xfc65c) DESTROYED Foo=HASH(0xfc65c) after if Attempt to free unreferenced scalar: SV 0xfc65c.
Note that the object now is destroyed at the end of the if(), but that we get a warning at global destruction as a bonus.

Of course, there is a much simpler method: just add an extra scope!

{ if (my $object = Foo->new) { } } print "after if\n"; package Foo; sub new { my $self = bless {},shift; print "CREATED $self\n"; $self } sub DESTROY { print "DESTROYED $_[0]\n" } __END__ CREATED Foo=HASH(0xfc65c) DESTROYED Foo=HASH(0xfc65c) after if

Hope this maybe helpful for someone someday.

Liz

Comment on Lexicals in if() scope gotcha!
Select or Download Code
Re: Lexicals in if() scope gotcha!
by dragonchild (Archbishop) on Mar 31, 2004 at 00:25 UTC
    Is there any reason why it gets an extra refcount? On first examination, there doesn't seem to be any good reason to give it the extra refcount ...

    Read BrowserUk's excellent response further down the thread for why I <strike>d my kneejerk response.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      ...my kneejerk response.

      Don't be so hard on yourself. This is hardly an intuitive issue :)

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Lexicals in if() scope gotcha!
by simonm (Vicar) on Mar 31, 2004 at 00:36 UTC
    Yikes! I've used the if ( my $foo = ... structure here and there in my code under the assumption that it would be scoped so as to DWIM; I'll have to keep my eye out for this...

    Update: On second thought, this is less of a problem than it seemed at first; all of those uses are within subroutine blocks that will ensure the referenced values don't stick around forever.

      Careful with your wording, the variable does go out of "scope" at the end of the "if" block, but the object refrenced by that variable refrences does not neccessarily get garbage collected.

      (As opposed to old ANSI C, where the variable itself was still in scope ... oy, those days sucked.)

Re: Lexicals in if() scope gotcha!
by hossman (Prior) on Mar 31, 2004 at 00:44 UTC
    1. This is not just an issue with "if" it also happens with "for" (but not foreach, or while)...
      #!/usr/bin/perl print "foreach...\n"; foreach (Foo->new) {} print "after foreach\n"; print "while...\n"; while (my $object = Foo->new && 0) {} print "after while\n"; print "for...\n"; for (my $object = Foo->new; 1 < 0; ) {} print "after for\n"; package Foo; sub new { my $self = bless {},shift; print "CREATED $self\n"; $self } sub DESTROY { print "DESTROYED $_[0]\n" } __END__ bester:~/tmp> monk.pl foreach... CREATED Foo=HASH(0x22494) DESTROYED Foo=HASH(0x22494) after foreach while... CREATED Foo=HASH(0x305f0) DESTROYED Foo=HASH(0x305f0) after while for... CREATED Foo=HASH(0x22494) after for DESTROYED Foo=HASH(0x22494)
    2. This is why you should allways head the immortal words of perltoot...
      Perl's notion of the right time to call a destructor is not well-defined currently, which is why your destructors should not rely on when they are called.
      So for and foreach are not just aliases for each other, as I've seen elsewhere.

      Update: So they are aliases, but there are still 2 entities, and the entity chosen depends on context.

      -QM
      --
      Quantum Mechanics: The dreams stuff is made of

        They are aliases. If you look, you see that for and foreach have been used differently. This should clarify that.

        #!/usr/local/bin/perl5.9.1 print "foreach1...\n"; foreach (Foo->new) {} print "after foreach\n"; + print "for...\n"; for (my $object = Foo->new; 1 < 0; ) {} print "after for\n"; + print "foreach2...\n"; foreach (my $object = Foo->new; 1 < 0; ) {} print "after foreach\n"; + package Foo; sub new { my $self = bless {},shift; print "CREATED $self\n"; $self } sub DESTROY { print "DESTROYED $_[0]\n" } __END__ foreach1... CREATED Foo=HASH(0x81249dc) DESTROYED Foo=HASH(0x81249dc) after foreach for... CREATED Foo=HASH(0x8124ac0) after for foreach2... CREATED Foo=HASH(0x81249dc) after foreach DESTROYED Foo=HASH(0x81249dc) DESTROYED Foo=HASH(0x8124ac0)

        Cheers,
        Ovid

        New address of my CGI Course.

        There's a for-loop, with three expressions (sometimes called "C-style for").

        There's a foreach-loop, with single scalar (sometimes implied with $_) and a list of values to be walked (sometimes called a "csh-style foreach").

        They're definitely two different kinds of loops. However the text for and foreach may be used to introduce either one. The actual loop-style is determined by what follows.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

      I got slightly different results but I also changed the foreach a bit...

      #!/usr/bin/perl print "foreach...\n"; foreach (my $object = Foo->new) {} print "after foreach\n"; print "while...\n"; while (my $object = Foo->new && 0) {} print "after while\n"; print "for...\n"; for (my $object = Foo->new; 1 < 0; ) {} print "after for\n"; package Foo; sub new { my $self = bless {},shift; print "CREATED $self\n"; $self } sub DESTROY { print "DESTROYED $_[0]\n" } __END__ foreach... CREATED Foo=HASH(0x813361c) after foreach while... CREATED Foo=HASH(0x8133700) DESTROYED Foo=HASH(0x8133700) after while for... CREATED Foo=HASH(0x813cfcc) after for DESTROYED Foo=HASH(0x813cfcc) DESTROYED Foo=HASH(0x813361c)

Re: Lexicals in if() scope gotcha!
by BrowserUk (Pope) on Mar 31, 2004 at 00:55 UTC
    #! perl -slw use strict; { package Foo; sub new{ bless {}, $_[ 0 ]; } sub DESTROY{ print "destroying $_[0]" } } if( my $x = Foo->new ) { print "Created $x"; } print "After first if"; if( my $x = Foo->new and 0 ) { print "Created $x"; } else{ print "Created $x but failed the if"; } print "After second if/else"; { if( my $x = Foo->new ) { print "Created $x"; } print "After third if"; } print "exited block"; __END__ P:\test>test Created Foo=HASH(0x22501c) After first if Created Foo=HASH(0x225028) but failed the if After second if/else Created Foo=HASH(0x225040) After third if destroying Foo=HASH(0x225040) exited block destroying Foo=HASH(0x225028) destroying Foo=HASH(0x22501c)

    When you create a lexical within the condition of an if statement, the scope is notionally the body of the if block, but as shown above in the second example, the scope actually has to extend to any else block also.

    To achieve this, the life of the lexical has to extend beyond the life of the if block, and so in the first two examples, whilst it's scope is defined by the if/else construct, it's life is bounded by the package level. In effect, it becomes a closure(*incorrect, but common term), within those blocks, but cannot be destroyed until the surrounding scope (the package main in this case) exits.

    In the third example, there is a scope enclosing the if construct and so the variable can be destroyed when that enclosing scope is exited, rather than waiting for package to finish.

    I guess setting the refcount to 2 is the mechanism by which closures(*) work?


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
      Out of curiosity, is the same true of while loops and foreach loops, where continue blocks might place the same constraints on lexicals as else blocks do for if constructs?


      Dave

        Yes.
Re: Lexicals in if() scope gotcha!
by ambrus (Abbot) on Mar 31, 2004 at 21:29 UTC

    Update: This node is based on a mistake, the statements below are false. See Re: Re: Lexicals in if() scope gotcha!.

    I fell through this trap when I wrote something like this:

    for my $n (1..256) { .... do something .... } for my $n (1..32) { ... do sthg else ... }
    and I got a warning for redefining $n.

    The difference is that as for localizes the looping variable (even if it's not my, but a global), it's just stupid to have it outside the loop's scope. It might be useful to access it after the loop when you're writing a for(;;) loop or an if() conditional.

    While this is a bit counter-intuitive for me too, it does have some logic. Perl is just not C++ (where you can put a declaration in the first argument of for(;;;)).

      Are you sure?
      use strict; use warnings; foreach my $foo (1..10 ) { print $foo } foreach my $foo (1..10 ) { print $foo } __END__ 1234567891012345678910
      I'm not seeing any errors or warnings using Perl's from 5.6.2 -> 5.9.0. I would be surprised if it would produce any warnings or errors, as this is an idiom that I use myself from time to time (without warnings, I might add).

      Liz

        Ok, you're right. I can't reproduce the error now, so I must have made some error like declaring $n inside the for loop's body or something like that.

        Thanks for clarifying this.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://341138]
Approved by broquaint
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (14)
As of 2014-09-19 16:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (143 votes), past polls