Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What magic in "our" or "use vars" satisfies "use strict qw(vars)"?

I have working code, but I am trying to understand why it works. I am also trying to learn more about the internals of Perl 5 (perlbrew, perl-5.26.1, Cygwin x64).

I know from perlvar and strict that use strict 'vars' works by setting flags in $^H. Perl then tests accesses to non-:: variables based on those flags. Somehow, both our and use vars mark variables so that they will pass the test. How do they do so?

For example:

perl -E 'package Foo;
    use strict "vars";
    use vars qw($foo);
    say $foo;'

runs fine (although it produces no output). Based on the source for use vars, I tried this, which I thought would have the same effect:

perl -E 'package Foo;
    use strict "vars";
    my $sym = "Foo::foo";          # <-- These two lines pulled straight
    *$sym = \$$sym;                # <-- from the source for the vars pragma
    say $foo;'

However, it gave me an error: Global symbol "$foo" requires explicit package name. I also tried $sym = "::Foo:foo" in the above, with the same result.

I checked, and $Foo::foo is in the symbol table:

$ perl -E 'package Foo;
      use Data::Dumper;
      use strict "vars"; 
      my $sym = "Foo::foo";
      *$sym = \$$sym; 
      say Dumper(\%{"Foo::"});'    # <-- Foo's symbol table

Output: 
$VAR1 = {
    'BEGIN'  => *Foo::BEGIN,
    'Dumper' => *Foo::Dumper,
    'foo'    => *Foo::foo          # <-- yep, it's there
};

What else is use vars doing that I'm missing? Does our do the same, or something different?

Update

Here's an A/B based on melpomene's answer:

Fails                         Succeeds
-------------------------     ----------------------------------
package Foo;                  package Foo;
use strict "vars";            use strict "vars";

                              BEGIN {
                                  package Bar;
my $sym="Foo::foo";               my $sym = "Foo::foo";
*$sym = \$$sym;                   *$sym = \$$sym;
                              }
say $foo;                     say $foo;
like image 512
cxw Avatar asked Apr 09 '18 17:04

cxw


1 Answers

use strict 'vars' works by setting flags in $^H.

Yes, but that's an implementation detail. $^H exposes some internal interpreter state bits, but you're not supposed to touch it in normal code.

Somehow, both our and use vars mark variables so that they will pass the test. How do they do so?

This is also considered an implementation detail.


However, we can peek a bit under the hood. strict "vars" complains about undeclared variables (at compile time).

There is a hardcoded list of variables that are exempt from this check; it includes all punctuation variables (e.g. $/, $_, etc. along with $a and $b (used by sort)).

All lexically (i.e. locally) declared variables also pass strict; this is how my, our, and state work. (For our purposes local is not a declaration and does not create local variables; local temporarily changes the value of an existing variable.)

The third exception is variables exported from modules. Using global variables as part of your module interface is generally considered to be a bad idea, but some older modules still do it. English also exports variables because that's its whole point, so we'll use it as an example:

use strict;
use English qw($INPUT_RECORD_SEPARATOR);
$INPUT_RECORD_SEPARATOR = "";  # <--
my $paragraph = readline STDIN;

The line marked <-- does not throw an error because Perl remembers which variables were imported from a module.

What does "exporting" actually mean? It just means aliasing a symbol across package boundaries:

*main::foo = \$Some::Module::foo;  # now $main::foo is an alias for $Some::Module::foo

The curious thing is that as far as the Perl internals are concerned, a variable is "imported" if it has been aliased in some other package. It does not matter what it was aliased to; all that matters is where the aliasing happened. use vars (ab-)uses this detail to bypass strict "vars" by exporting your own variables back at you:

package Some::Package;
use vars qw($foo);

works like

package Some::Package;
BEGIN {
    package vars;
    *Some::Package::foo = \$Some::Package::foo;
}
# now $foo is an alias to ... itself

The other piece of the puzzle is that use happens at compile time, like BEGIN blocks. Your example fails because your aliasing attempt only happens at runtime, which is too late for strict, and because it doesn't switch to a different package to do the aliasing.


In the end vars is just a module, written in plain Perl. our is different: It is a real keyword and part of the language. It also has different behavior: It effectively creates an alias (to a package variable), but that alias lives in a local scope, not the symbol table.

Consider e.g. the following:

my $foo = 2;
{
    our $foo = "hello";
    print "foo = $foo; main::foo = $main::foo\n";
}
print "foo = $foo; main::foo = $main::foo\n";

This outputs

foo = hello; main::foo = hello
foo = 2; main::foo = hello

because the inner our $foo declaration shadows the outer $foo in the inner block. Within the block both $foo and $main::foo refer to the same variable; outside $foo refers to the lexical my $foo, which is untouched.

Another difference to use vars:

use strict;
package Foo;
our $x = "hello";
package Bar;
print "$x\n";  # hello

This code works fine because package declarations don't create a new scope. There is only one unit of scoping here (the whole file), and so our $x makes $x refer to $Foo::x for the rest of the file, no matter which package you switch into.

On the other hand:

use strict;
package Foo;
use vars qw($x);
$x = "hello";
package Bar;
print "$x\n";

This code doesn't even compile. The reference to $x in the last line can't be resolved: Perl checks the local scope first, but there are no locally declared $x's. It then checks the current package (Bar) and finds nothing either, and without strict "vars" it would have automatically created $Bar::x for you, but with strict "vars" enabled this is simply an error. $Foo::x is irrelevant and never checked.

like image 176
melpomene Avatar answered Oct 03 '22 22:10

melpomene