Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interaction between 'Exporter', 'use vars', and 'local'

Tags:

scope

perl

I have tried to boil this behavior down to the simplest test case. Consider the following two modules:

Bar.pm

package Bar;

use base 'Exporter';
use vars qw/ $BarVar /;
BEGIN { @EXPORT_OK = qw/ $BarVar /; }

$BarVar = 'original';

1;

Foo.pm

package Foo;

use Bar qw/ $BarVar /;

sub foo { print $BarVar . "\n"}

1;

Now, the output of the following script -

use strict;
use warnings;

use Foo;

{
  local $Bar::BarVar = 'modified'; 
  Foo::foo();
}

Foo::foo();

is "original" printed twice, where I would have expected it to be "modified" followed by "original", because I would have expected the local declaration to replace the package variable $Bar::BarVar throughout its whole scope, which includes the first call to foo(). What's the explanation? How can I locally override $Bar::BarVar?

like image 461
gcbenison Avatar asked Aug 06 '15 19:08

gcbenison


2 Answers

You may have heard me say that my and our create variables (the latter of which is aliased), but that local just makes a temporary backup of the value.

I lied.

local does make a backup, but not of the value. It backs up the address of the associated scalar, creates a new scalar, and associates the name with the new scalar. In this case, that leaves $Bar::BarVar referring to the new scalar, and $Foo::BarVar referring to the old scalar.

$ perl -E'
   *x = \$y;  say \$x, " - ", \$y;
   local $y;  say \$x, " - ", \$y;
'
SCALAR(0x44d7c70) - SCALAR(0x44d7c70)
SCALAR(0x44d7c70) - SCALAR(0x44ba130)

If you were to actually backup just the value, the problem would go away.

use Sub::ScopeFinalizer qw( scope_finalizer );

{
   my $backup = $Bar::BarVar;
   my $guard = scope_finalizer { $Bar::BarVar = $backup };
   $Bar::BarVar = 'modified'; 
   Foo::foo();
}

A more specialized tool might exist.

like image 193
ikegami Avatar answered Nov 09 '22 13:11

ikegami


See What Not to Export:

There's one more item to add to this list. Do not export variable names. Just because Exporter lets you do that, it does not mean you should.

 @EXPORT_OK = qw($svar @avar %hvar); # DON'T!

Exporting variables is not a good idea. They can change under the hood, provoking horrible effects at-a-distance that are too hard to track and to fix. Trust me: they are not worth it.

To provide the capability to set/get class-wide settings, it is best instead to provide accessors as subroutines or class methods instead.

There is one exception this rule: If you have constant variables (such those created using Const::Fast), you can export them because, by definition, they can't create effects at a distance.

like image 44
Sinan Ünür Avatar answered Nov 09 '22 11:11

Sinan Ünür