Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can someone explain why Perl behaves this way (variable scoping)?

Tags:

perl

My test goes like this:

use strict;
use warnings;

func();
my $string = 'string';
func();

sub func {
    print $string, "\n";
}

And the result is:

Use of uninitialized value $string in print at test.pl line 10.

string

Perl allows us to call a function before it has been defined. However when the function uses a variable declared only after the function call, the variable appears to be undefined. Is this behavior documented somewhere? Thank you!

like image 397
darkgrin Avatar asked Nov 18 '15 15:11

darkgrin


2 Answers

The behaviour of my is documented in perlsub - it boils down to this - perl knows $string is in scope - because the my tells it so.

The my operator declares the listed variables to be lexically confined to the enclosing block, conditional (if/unless/elsif/else), loop (for/foreach/while/until/continue), subroutine, eval, or do/require/use'd file.

It means it's 'in scope' from the point at which it's first 'seen' until the closing bracket of the current 'block'. (Or in your example - the end of the code)

However - in your example my also assigns a value.

This scoping process happens at compile time - where perl checks where it's valid to use $string or not. (Thanks to strict). However - it can't know what the value was, because that might change during code execution. (and is non-trivial to analyze)

So if you do this it might be a little clearer what's going on:

#!/usr/bin/env perl
use strict;
use warnings;

my $string; #undefined
func();
$string = 'string';
func();

sub func {
    print $string, "\n";
}

$string is in scope in both cases - because the my happened at compile time - before the subroutine has been called - but it doesn't have a value set beyond the default of undef prior to the first invocation.

Note this contrasts with:

#!/usr/bin/env perl
use strict;
use warnings;

sub func {
    print $string, "\n";
}

my $string; #undefined
func();
$string = 'string';
func();

Which errors because when the sub is declared, $string isn't in scope.

like image 85
Sobrique Avatar answered Nov 16 '22 00:11

Sobrique


First of all, I would consider this undefined behaviour since it skips executing my like my $x if $cond; does.

That said, the behaviour is currently consistent and predictable. And in this instance, it behaves exactly as expected if the optimization that warranted the undefined behaviour notice didn't exit.


At compile-time, my has the effect of declaring and allocating the variable[1]. Scalars are initialized to undef when created. Arrays and hashes are created empty.

my $string was encountered by the compiler, so the variable was created. But since you haven't executed the assignment yet, it still has its default value (undefined) during the first call to func.

This model allows variables to be captured by closures.

Example 1:

{
   my $x = "abc";
   sub foo { $x }   # Named subs capture at compile-time.
}

say foo();  # abc, even though $x fell out of scope before foo was called.

Example 2:

sub make_closure {
   my ($x) = @_;
   return sub { $x };   # Anon subs capture at run-time.
}

my $foo = make_closure("foo");
my $bar = make_closure("bar");

say $foo->();  # foo
say $bar->();  # bar

  1. The allocation is possibly deferred until the variable is actually used.
like image 22
ikegami Avatar answered Nov 16 '22 01:11

ikegami