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

1

11

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;
Dermott answered 9/4, 2018 at 17:48 Comment(1)
The lexical variables declared by my, state and our exempt from strict. use vars creates a package variable in a different package and imports it, taking advantage of the fact that imported variables are exempt from strict.Polak
P
17

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.

Popedom answered 9/4, 2018 at 18:34 Comment(4)
Yes, magic indeed! To confirm, is this correct?--- our doesn't need to trigger "exported if aliased when in a different package" because it is updating the local scope (scratchpad). Perl checks the local scope and finds the our variable before it checks the symbol table for an exported variable.Dermott
@Dermott That's correct, but I haven't heard the term "scratchpad" in this context before. There's a thing called a "(lexical) pad", which is a data structure used to implement local scopes in the interpreter.Popedom
Thanks very much! This is very helpful - I'm pretty sure I would not have figured it out even if I had tried spelunking in the C source. (I got scratchpad from here, but I'll use "lexical pad" in future.)Dermott
@Dermott Oh, interesting. If that link is right, "pad" is just a shortened name for "scratchpad".Popedom

© 2022 - 2024 — McMap. All rights reserved.