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

Dear monks,

I am experimenting with perl modules, and am stuck: I have the package p2 (see below), and am calling the subs saveP and loadP from my main script. When loading and evaluating the data, I get the error message

Variable "$gold" is not available at (eval 15) line 1, <GEN7> line 1.
I am confused: why is $gold visible in saveP (when I write it), but not in the eval?

And how can/should I resolve this?

Please enlighten me! Rata

In my main Tkx-script - called from a menu:

sub saveP{ 	p2::saveP(); }
sub loadP{	p2::loadP(); }

My package p2:

package p2;

use strict;
use warnings;
use Data::Dumper;
use FileHandle;

my $gold = 5000;
1; 

#====================================================================================================
sub saveP
{
	my $savefile = new FileHandle("xxx.ptysav", "w");
	$savefile->print("\$gold = $gold;\n");
	close ($savefile);
}
#====================================================================================================
#                       note: eval on files (or strings) is dangerous - see the reply of afoken below
#                                                         https://www.perlmonks.org/?node_id=11161417
sub loadP
{
	my $savefile = new FileHandle("xxx.ptysav", "r");
	my $lines  = join("",($savefile->getlines()));
	my $result = eval $lines;                        # <- here is the eval
	close ($savefile);	
}

The save-file is as expected: $gold = 5000;

Replies are listed 'Best First'.
Re: trouble with packages/eval/variable-scoping
by LanX (Saint) on Aug 29, 2024 at 12:06 UTC
    $gold is not explicitly used in loadP() but in saveP().

    Hence it's not in the closure of loadP and the dynamic eval can't find it.

    That's how Perl decides to populate the so called variable (Scratch)Pad of a sub at compile time. ¹

    Workarounds
    • explicitly name all private vars you need inside the sub
    • otherwise use localized package variables
    • UPDATE have the private vars available in the scope of the caller(s) ²

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

    Updates

    ¹) see also PadWalker

    ²) see Re^2: trouble with packages/eval/variable-scoping (caller's scope)

      Thanks a lot, LanX!

      So I changed the sub loadP to the following, and it works :-)

      Rata (having learned something today)

      
      sub loadP
      {
      	$gold = 0;                                              # <--- new
      	my $savefile = new FileHandle("xxx.ptysav", "r");
      	my $lines  = join("",($savefile->getlines()));
      	my $result = eval $lines;
      	close ($savefile);	
      }
      

        IIRC, this also works:

        $gold if 0;

        It doesn't modify the var. It doesn't result in any run-time code at all.

        Demo:

        $ perl -Mv5.10 -we \ 'do { my $x = 42; sub { say eval q{$x} } }->()' Variable "$x" is not available at (eval 1) line 1. Use of uninitialized value in say at -e line 1. $ perl -Mv5.10 -we \ 'do { my $x = 42; sub { $x if 0; say eval q{$x} } }->()' 42

        Update: Tested and added demo.

Re: trouble with packages/eval/variable-scoping
by afoken (Chancellor) on Aug 29, 2024 at 17:46 UTC
      > Imagine someone changing xxx.ptysav

      That's not very different to use , require or do FILE

      The essential question is "who has the necessary rights to modify FILE".

      (Or any code in general)

      Where in the OP's post do you see that it's an open config file accessible by others?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

        Where in the OP's post do you see that it's an open config file accessible by others?

        Trouble is, it's not just "others" that you have to worry about. In your config file it is your own typos, etc. Config files should be parsed, not executed. And if for some odd reason they must be executed, do it at compile time. And if for some other reason you have to do it at run-time then you'd better be using taint mode.


        🦛

Re: trouble with packages/eval/variable-scoping (Version?)
by LanX (Saint) on Aug 29, 2024 at 16:03 UTC
    Hmm which Perl version / OS did you use?

    I don't have your problems, but only tested on my mobile with termux.

    use v5.12; use warnings; say $]; my $t = 42; sub foo { say eval $_[0] } foo('$t'); -UUU:----F1 t_dyna_scope.pl All L4 (Perl) ---- -*- mode: compilation; default-directory: "~/perl/" \ -*- Compilation started at Thu Aug 29 18:00:33 perl t_dyna_scope.pl 5.036000 42 Compilation finished at Thu Aug 29 18:00:33

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

      Answering my own question, $t was still in the pad of the calling scope in my example. ¹

      Otherwise the problem persists:

      use v5.12; use warnings; say $]; { my $t = 42; sub foo { say eval $_[0] } } foo('$t'); -UUU:----F1 t_dyna_scope.pl All L13 (CPerl) --- -*- mode: compilation; default-directory: "~/perl/" \ -*- Compilation started at Fri Aug 30 11:25:02 perl t_dyna_scope.pl 5.036000 Variable "$t" is not available at (eval 1) line 1. Use of uninitialized value in say at t_dyna_scope.pl\ line 10. Compilation finished at Fri Aug 30 11:25:02

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

      Update

      ¹) interestingly a way to access private vars in the dynamic scope ... Quite surprising

      Update

      It's not dynamic scope either, probably only file scope... Needs further testing