Reputation: 468
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
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
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