scozy
scozy

Reputation: 2582

Why doesn't perl care about whether I've declared my variable under stricture?

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

Answers (2)

ikegami
ikegami

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

Miller
Miller

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

Related Questions