Reputation: 2582
I spent too much time today debugging one of those insidious small mistakes.
Here is more or less what I did:
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
my $test = new MyTest('test');
say $test->to_string for 1..3;
package MyTest;
sub new {
my $class = shift;
my $parm = shift;
return bless \$parm, $class;
}
sub to_string {
my $self = shift;
my $string = 'Hello!' if 0;
$string .= $$self;
return $string;
}
Notice that because of the false test, my $string
is not evaluated. (Of course, the code I lost so much time on didn't have if 0
!)
I realise that the compiler doesn't know that the test will be false, but shouldn't it complain that I am using a variable that may not have been declared at the following line?
Also, I would expect this to result in a run-time error, but perl
just gives me
test
testtest
testtesttest
Why is there no error? Is this a bug or a feature?
Upvotes: 2
Views: 74
Reputation: 385897
my $foo;
is suppose to be allocate a new var (kinda like Scalar* foo = new Scalar();
), but that would be quite inefficient, so it's not implemented that way.
my
has the compile-time effect of declaring the variable. This effectively creates it. Since it happens at compile time, it's unaffected by the if
. As usual, the variable is scoped the block (curlies) containing it.
my
has the run-time effect of putting a directive on the stack to replace the variable with a fresh one on scope exit. (If nothing grabbed a reference to it, it simply gets cleared instead.) This is what's being skipped.
Using a variable under different conditions than the ones under which it's declared is undefined (disallowed) behaviour.
If you're trying to create a lexically-scoped persistent variable, use
{
my $x = init();
sub foo {
... $x ...
}
}
or
use feature qw( state ); # Require 5.10+
sub foo {
state $x = init(); # init() is only called the first time foo() is called.
... $x ...
}
Upvotes: 7
Reputation: 35198
Here's a tighter example:
use strict;
use warnings;
sub to_string {
my $param = shift;
my $string = 'Hello!' if 0;
$string .= $param;
return $string;
}
print to_string("foo"), "\n";
print to_string("bar"), "\n";
print to_string("baz"), "\n";
Outputs:
foo
foobar
foobarbaz
Basically, you should never declare and conditionally initialize a variable using the syntax my $var = $val if $cond
, as the behavior will be unexpected. Perl Bug 5 - my $var = val if (..) doesn't work properly
Instead always use the ternary in situations like that:
my $var = $cond ? $val : undef;
Upvotes: 5