Rhandom has asked for the wisdom of the Perl Monks concerning the following question:

I am wondering about some weird behavior I am seeing and am wondering if there is anybody who might have an explanation.

I have below, three instantiations of objects that upfront seem like they should all have the same result. I am attempting to tie the object the scope of the "if" block and have the DESTROY method called as the if block leaves scope. The first two methods work properly, but the third does not. You cannot access $obj outside any of the "if" blocks - even after the third block - but the object does not DESTROY after the final block -- only when the program stops running.

Can anybody explain what is happening here, and if so, is the file still in scope somewhere in the internals or what? I have tried this on older perls as well with the same result.

#! /usr/bin/perl -w package MyPack; use strict; sub new { return bless {},__PACKAGE__; } sub DESTROY { print "I'm destroyed\n"; } print "[$]]\n\n"; if( 1 ){ my $obj = MyPack->new(); print "Inside the IF (1)\n"; } print "Outside the IF (1)\n"; print "\n"; if( defined(my $obj = MyPack->new() ) ){ print "Inside the IF (2)\n"; undef $obj; } print "Outside the IF (2)\n"; print "\n"; if( defined(my $obj = MyPack->new() ) ){ print "Inside the IF (3)\n"; } print "Outside the IF (3)\n";

This prints out:
[5.006] Inside the IF (1) I'm destroyed Outside the IF (1) Inside the IF (2) I'm destroyed Outside the IF (2) Inside the IF (3) Outside the IF (3) I'm destroyed




my @a=qw(random brilliant braindead); print $a[rand(@a)];

Replies are listed 'Best First'.
Re: Object scope and DESTROY
by perrin (Chancellor) on Sep 24, 2001 at 21:05 UTC
    Your code demonstrates the fact that the conditional statement of an "if" has the scope of the surrounding block. If you need something created in it to go out of scope, you could try this:
    { if( defined(my $obj = MyPack->new() ) ){ print "Inside the IF (3)\n"; } } print "Outside the IF (3)\n";
      Thank you for the prompt reply. This certainly would work. What I am looking for though is an explanation of why, even though the object can't be accessed (I would assume it is out of scope), the DESTROY method is still not called.

      UPDATE: I notice that the above post mentions the scope is that of the block surrounding the if. If that is true (I don't know if it is or not) why do I get a compile error when I add the following line at the end of my code (assuming that the $obj should still be in scope):
      print "[".ref($obj)."]\n";


      Any ideas?

      my @a=qw(random brilliant braindead); print $a[rand(@a)];
        Well, that makes it look like it isn't the scope of the surrounding block either, so I'm not sure what scope it has. I suppose you could compare this with what happens when you just do a "my $obj = MyPack->new()" before the last "if".
Re: Object scope and DESTROY
by stefp (Vicar) on Sep 24, 2001 at 21:35 UTC
    Objects that have gone out of scope and not referenced elsewere are destroyed when exiting the routine or module that contains the said scope. This is done by the opcode leave as you can convince yourself by examining the following program and its trace.

    program:

    package A; sub DESTROY { print "destroy\n"; } sub new { bless {}, A } package main; my ($a, $o) = ( 1 ); if ( $a ) { my $a = A->new; print "scope left\n"; }

    trace:

    ./perl -Dt /tmp/test (/tmp/test:3) null (/tmp/test:3) const(PV("destroy\12"\0)) (/tmp/test:3) stringify (/tmp/test:11) null (/tmp/test:11) const(PV("scope left\12"\0)) (/tmp/test:11) stringify EXECUTING... (/tmp/test:0) enter (/tmp/test:0) nextstate (/tmp/test:8) pushmark (/tmp/test:8) const(IV(1)) (/tmp/test:8) pushmark (/tmp/test:8) padsv($a) (/tmp/test:8) padsv($o) (/tmp/test:8) aassign (/tmp/test:8) nextstate (/tmp/test:9) padsv($a) (/tmp/test:9) and (/tmp/test:9) enter (/tmp/test:9) nextstate (/tmp/test:10) pushmark (/tmp/test:10) const(PV("A"\0)) (/tmp/test:10) method_named (/tmp/test:10) entersub (/tmp/test:10) nextstate (/tmp/test:4) pushmark (/tmp/test:4) anonhash (/tmp/test:4) srefgen (/tmp/test:4) const(PV("A"\0)) (/tmp/test:4) bless (/tmp/test:4) leavesub (/tmp/test:10) padsv($a) (/tmp/test:10) sassign (/tmp/test:10) nextstate (/tmp/test:11) pushmark (/tmp/test:11) const(PV("scope left\12"\0)) (/tmp/test:11) print scope left (/tmp/test:11) leave # this "leave" triggers the DESTROY + call (/tmp/test:9) nextstate (/tmp/test:3) pushmark (/tmp/test:3) const(PV("destroy\12"\0)) (/tmp/test:3) print destroy (/tmp/test:3) leavesub (/tmp/test:9) leave

    -- stefp

(tye)Re: Object scope and DESTROY
by tye (Sage) on Sep 24, 2001 at 22:21 UTC

    I played with this a bit and it certainly appears to be a bit of a bug in Perl. The lexical variable is no longer available after the if block but the reference is not destroyed until the next enclosing block is left.

    So lexical variables declared inside the conditional of an if block appear to be partly scoped to inside the if block and partly scoped in the scope enclosing the if. Almost sounds reasonable. Almost.

            - tye (but my friends call me "Tye")
Re: Object scope and DESTROY
by derby (Abbot) on Sep 24, 2001 at 21:50 UTC
    Rhandom,

    Just as stefp pointed out with a concrete example, here's the word as delivered in perltoot.

    Destruction happens automatically via Perl's garbage collection (GC) system, which is a quick but somewhat lazy reference-based GC system. To know what to call, Perl insists that the destructor be named DESTROY. 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.

    -derby

    Update: Geez I have an itchy submit finger today. If we also look at perlguts:

    Perl uses an reference count-driven garbage collection mechanism. SVs, AVs, or HVs (xV for short in the following) start their life with a reference count of 1. If the reference count of an xV ever drops to 0, then it will be destroyed and its memory made available for reuse.
    This normally doesn't happen at the Perl level unless a variable is undef'ed or the last variable holding a reference to it is changed or overwritten.

    So what's happening in your code is the first scalar has it's reference count drop to zero. At the end of the block, GC kicks in. In the second case, you pre-maturely force the reference count to zero with undef which cause the scalar to be GC'ed not at the block it's defined in but the first block which completes (and GC kicks in). The third case shows the scalar being GC when it's supposed to be.