BryKKan
BryKKan

Reputation: 468

Use Lexical Variable Declared in For Loop in Continue Block

I am having trouble getting one of my modules to compile with what looks a variable scoping issue, but I don't understand why.

Sample Code:

PATH: foreach my $path (@paths) {
    open(my $file, '<', $path) or next PATH;

    my %properties;
    LINE: while (<$file>) {
        $_ =~ /$property_regex/ or next LINE;
        $properties{$1} = $2;
    }

    foreach (@property_keys) {
        unless ($properties{$_}) {
            # do stuff
            next PATH;
    }

    if ( $irrelevant_condition ) {
        # do stuff
        next PATH;
    }

    foreach my $new_var (@new_list) {
        # do stuff (does not iterate PATH loop)
    }
} continue {
    if (defined $file) { close($file) or die; }
}

Translated to the stripped down code above, the error I'm getting is:

Global symbol "$file" requires explicit package name at line 25

Namely, it seems to be complaining about the use of $file in the continue block at the bottom. As you can see, $file is declared as a lexical variable on line 2, within the outer foreach loop (labelled PATH).

However, based on the perldoc for continue, I would expect $file to still be in scope:

[...] it is always executed just before the conditional is about to be evaluated again, just like the third part of a for loop in C. Thus it can be used to increment a loop variable, even when the loop has been continued [...]

In order to be able to increment a loop variable, wouldn't the continue block have be treated as part of the same lexical scope as the loop to which it is attached?

What am I missing?


Note: This module is a Moo class, so while I do not have an explicit use strict statement anywhere, when you use Moo we enable strict and warnings.

Upvotes: 3

Views: 190

Answers (2)

ikegami
ikegami

Reputation: 386361

The loop variable is garanteed to have been declared before the continue block is evaluated, so that variable can be provided to the continue block.

But any part of the the loop block can be skipped (e.g. using next), so Perl doesn't know at compile-time which of the loop block's variables will be declared when the continue block is entered, so it can't make any of them available to the continue block.


If you're not content with the file handle getting closed automatically (e.g. because you want to detect write errors), you could use a scope guard instead of a continue block.

use Sub::ScopeFinalizer qw( scope_finalizer );

for my $qfn (@qfns) {
   my $fh;
   my $guard = scope_finalizer {
      if ($fh) {
         close($fh)
            or die("Error writing to \"$qfn\": $!\n");
      }
   };

   open($fh, '>', $qfn)
      or die("Can't create \"$qfn\": $!\n");

   ...
}

Upvotes: 3

Tanktalus
Tanktalus

Reputation: 22294

Your $path variable is still in scope inside the continue BLOCK, but the stuff inside for's BLOCK is out of scope because you've reached the end of that BLOCK (you've hit the end brace / exited the braces). The $path variable, however, is not inside the braces, so can be visible inside the continue BLOCK (even though it's not visible beyond that).

If the syntax were like this:

for (...)
{
 $x = stuff();
 continue { more_stuff($x) }
}

then I would expect $x to be visible. IIRC, perl 6 has stuff like this, but in perl 5, the continue BLOCK is outside the loop's block, and thus doesn't see lexical variables inside that block.

Upvotes: 4

Related Questions