No more false postfix lexical declarations in v5.30

Before Perl v5.10 introduced state variables, people did various things to create persistent lexical variables for a subroutine. With v5.30, one of those constructs is now a fatal error.

Often you want a persistent variable to be scoped and private to a subroutine. But, once you leave that scope, normal lexical variables disappear because their reference count drops to zero. So, no persistence.

You could make another scope and declare your variable in that. Your subroutine can see taht variable since they are both in the same scope. The subroutine is defined and references the variable, so the variable doesn’t disappear until the subroutine does:

{
my $persistent;

sub do_something {
	print $persistent++, "\n";
	}
}

That’s slightly ugly because you use an additional scope and you declare the variable outside of the subroutine. It makes more sense when you want to share a private variable between subroutines (discussed in Intermediate Perl and demonstrated in File::Find::Closures):

{
my $persistent;

sub increment {
	print ++$persistent, "\n";
	}

sub decrement {
	print --$persistent, "\n";
	}
}

But, there’s a highly discouraged, ancient trick that does the same thing. If you’ve never seen it, that’s a good thing.

Inside your subroutine, declare the lexical variable and attach a false postfix conditional to this:

#!/Users/brian/bin/perls/perl5.8.9
use strict;
use warnings;

do_something() for 1 .. 5;

sub do_something {
	my $x if 0;
	print $x++, "\n";
	}

Run this. The variable keeps its value:

0
1
2
3
4

This should have never worked, but it did and people starting using it. Instead of breaking the unintended use of a bug, it stuck around. For a long time, Perl tolerated weird and arcane constructs both in the names of backward compatibility and “we have better things to do”.

Change that to a true postfix conditional and you don’t get a persistent variable (because it was a bug that you ever did!):

sub do_something {
	my $x if 1;
	print $x++, "\n";
	}

Now it’s a new variable each time so it never remembers the value:

0
0
0
0
0

This was useful for a long time because Perl didn’t have a way to declare persistent, private variables.

But, Perl v5.10 introduced state to do the same thing; it’s executed once then never again:

sub do_something {
	state $x;
	print $x++, "\n";
	}

The history

Perl v5.10 (released December 2007!) introduced a warning to deprecate this (although you need warnings turned on to see it):

$ perl5.8.9  -e 'my $x if 0'
$ perl5.10.1 -w -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.

Notice that it’s only the false conditional. A true conditional gives no warning:

$ perl5.10.1 -e 'my $x if 1'

This deprecation continues to be the case for several years until v5.26 said “no, really this time”. The perl policy is that you get two minor versions warning before it actually happens (so, after v5.26 and v5.28, v5.30 can remove it):

$ perl5.12.0 -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.
$ perl5.14.2 -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.
$ perl5.16.3 -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.
$ perl5.18.2 -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.
$ perl5.20.1 -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.
$ perl5.22.1 -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.
$ perl5.24.1 -e 'my $x if 0'
Deprecated use of my() in false conditional at -e line 1.
$ perl5.26.3 -e 'my $x if 0'
Deprecated use of my() in false conditional. This will be a fatal error in Perl 5.30 at -e line 1.
$ perl5.28.1 -e 'my $x if 0'
Deprecated use of my() in false conditional. This will be a fatal error in Perl 5.30 at -e line 1.

Perl v5.30 at last fatalizes the questionable statement:

$ perl5.30.0 -e 'my $x if 0'
This use of my() in false conditional is no longer allowed at -e line 1.

But, this isn’t a solution to the entire problem. If you initialize the variable at the same time, you don’t get the error:

$ perl5.30.0 -e 'my $x = 0 if 0'

Diagnosis

Do you have this problem? Perl::Critic can tell you:

$ perlcritic declare-postfix.pl
Variable declared in conditional statement at line 8, column 2.  Declare variables outside of the condition.  (Severity: 5)