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

I have a global variable going out of scope and I'm not sure why. Below is a subset of my code. I have confirmed that all assert statements are true both by appropriate debug prints and using a debugger walk through. My global var just disappears.

For those wondering why I don't just pass it, its because in the real program there are functions under Bar that don't need/use gVar except for error/debug printing so I thought this was a cleaner way to pass rather than pass it constantly, often to functions it isn't used in.
my $gVar; sub Foo { my $listOfHashRef = generateList(); my $functionPtr; foreach $gVar (@$listOfHashRef) { $functionPtr = $gVar->{funPtr}; # Assert: $gVar has all of its values here $functionPtr->(); } } # Assert: The $gVar->{funPtr} does indeed point to Bar sub Bar { print "$gVar\n"; # Assert: $gVar is null }

Replies are listed 'Best First'.
Re: Tricky scope problem
by shmem (Chancellor) on Apr 10, 2009 at 21:03 UTC
    I have a global variable going out of scope

    Global variables don't go out of scope. That's because they are global. In the code you've posted there's no global variable at all. There's a $gVar which is lexically scoped via my on file level.

    Do you mean, $gVar doesn't hold the last hashref from $listOfHashRef after calling Foo() ? That's because the $gVar as a foreach iterator aliases (masks) the outer $gVar. Consider:

    use strict; sub generateList { my $ref; push @$ref, { funPtr => do { my $x = $_; sub { print "in $x\n" } } + } for qw(foo bar); $ref; } my $gVar; sub Foo { my $listOfHashRef = generateList(); my $functionPtr; foreach (@$listOfHashRef) { $gVar = $_; $functionPtr = $gVar->{funPtr}; # Assert: $gVar has all of its values here $functionPtr->(); } } # Assert: The $gVar->{funPtr} does indeed point to Bar sub Bar { print "$gVar\n"; # Assert: $gVar is null } Foo(); Bar(); __END__ in foo in bar HASH(0x88a1660)
      Thank you mighty perl Monks for your wisdom! (this is the orginal poster, I'll have to make a user name at some point).
      I do appologize for the confusion on debugging, all of your assumptions were correct and and the forloop was masking my outer variable. I was attempting to showcase the problem from a much larger program, after seeing your code snippits, I think I have a better idea of how to showcase things (I will read the linked page too, though I do point out, I knew what I meant, and you knew what I meant too, beginers luck?).
      I have read many web pages on perl scoping (global vs lexical) including the documentation on key words 'my' and 'our' packaged within the perl install. I'm afraid I still think of "my $var;" used out side of a function as global to the file which would be its C/C++ equivalent and it appears to be the case in perl (excepting its use in forloops). I tend to think of using "our $var;" as the equivalent of an external command in C/C++, a way to link a file global var across mulitple files. Is this incorrect?
      How does one declare a global variable if "use strict;" is in effect? As soon as I declare it via 'my', it is lexical not global.
        I tend to think of using "our $var;" as the equivalent of an external command in C/C++, a way to link a file global var across mulitple files.

        This is correct; but in all files this variable should be declared with our. But there's more to variables declared with 'our':

        • declaring a variable with 'our' creates a package global in the package in which it is declared
        • the short name of this variable is visible throughout its lexical scope, even spanning packages

        Example:

        package Foo; our $foo = 'foo'; # $Foo::foo now exists package main; print $foo,"\n"; # $Foo::foo, not $main::foo ! print $Foo::foo,"\n"; __END__ foo foo
Re: Tricky scope problem
by kennethk (Abbot) on Apr 10, 2009 at 21:00 UTC

    And you've made debugging tricky by not providing code that clearly replicates your issue - neither Foo nor Bar are ever called or referenced, your global $gVar is never assigned a value and the flow of your program is unclear. Please read I know what I mean. Why don't you?.

    Based on your comments, it looks like you expect to assign $gVar a value as a loop index and then maintain that value when Bar is called within the loop. However, when you use $gVar as the index on your foreach, you actually create a lexically scoped copy (or rather a local alias for the list element). When you then call Bar, it only sees the global. It's discussed (briefly) in Foreach Loops. Perhaps this example will provide clear flow:

    my $var = 5; foreach $var (1 .. 3) { print $var; print_var(); } print $var; sub print_var {print $var};

    Outputs: 1525355

    Update: Cleaned up some unclear language.

      Wow! Learn something new every day. I'm glad I've never run into this. It would have driven me nuts.

      So with that being said, do you have any idea why this is implemented in such a way? Why does it become "implicitly local to the loop", hiding the global var's value? This is completely unexpected (at least to me), and doesn't really seem like the logical thing.

        Why does it become "implicitly local to the loop", hiding the global var's value?
        Consider the following code:
        >perl -wMstrict -le "my $scalar = 42; print qq{scalar before subroutine: $scalar}; S(); print qq{scalar after subroutine: $scalar}; sub S { for $scalar (1 .. 3) { print qq{scalar in subroutine for-loop: $scalar}; } } " scalar before subroutine: 42 scalar in subroutine for-loop: 1 scalar in subroutine for-loop: 2 scalar in subroutine for-loop: 3 scalar after subroutine: 42
        In the  for loop in the subroutine, a temporary list is generated and  $scalar is aliased to each element of the list in turn. Then the subroutine exits. What happens to the temporary list upon exit from the subroutine (or probably upon exit from the loop, actually)? To what element of the list should  $scalar remain aliased after exit from the subroutine (or the loop)? The answer to this question seems to be 'None', since the list no longer exists. Hence, implicit loop locality.
        It's done this way to allow for the following magic:

        > perl -e '@arr = (1,2,3);$_++ foreach @arr;print @arr,"\n"' 234

        By aliasing the loop variable, we gain the power to use foreach (and grep and map...) to create simple list operations without dealing with index syntax.