muthm has asked for the wisdom of the Perl Monks concerning the following question:

Wise monks,
I have stumbled over a behavior that I cannot explain, and I am seeking your knowledge to help me understand better.

In the middle of editing and restructuring some code, I happened to have the initialization of a lexical variable at the beginning of a subroutine expressed like this:

my $my_var = "default" unless defined $param; $my_var //= $param;

Clearly, this could (and maybe should) be written much nicer as my $my_var = $param // "default"; but at that point I wanted to verify some other things and ran the above version.

When I called the subroutine several times for testing, with different parameters, I had results that seem strange to me.
The lexical variable, of which I thought it would be created as undef for each call to the subroutine, kept its value from one call to another if the condition for the initialization was false.

The following program shows what I mean:

#!/usr/bin/env perl use strict; use warnings; use feature 'say'; use feature 'signatures'; no warnings 'experimental::signatures'; use Data::Dump qw( pp ); sub foo( $param ) { say "\$param is ", pp( $param ); my $my_var = "default" unless defined $param; say "\$my_var after declaration is ", pp( $my_var ); say +( defined $param ? ! defined $my_var : defined $my_var && $my_var eq 'default' ) ? " (as expected)" : " (unexpected to me!)"; $my_var //= $param; say "\$my_var after '//=' is ", pp( $my_var ); say ""; return $my_var; } foo( $_ ) for undef, "call 1", "call 2", "call 3"; 1;
The output is:
$ ./my_with_if.pl $param is undef $my_var after declaration is "default" (as expected) $my_var after '//=' is "default" $param is "call 1" $my_var after declaration is undef (as expected) $my_var after '//=' is "call 1" $param is "call 2" $my_var after declaration is "call 1" (unexpected to me!) $my_var after '//=' is "call 1" $param is "call 3" $my_var after declaration is "call 1" (unexpected to me!) $my_var after '//=' is "call 1"

The point is that $my_var still contains the "call 1" value from the previous call in the "call 2" call.
Why isn't it undef?

I also find it interesting to see that $my_var *is* initialized to undef as I would expect in the "call 1" call, where the value from the call before was the "default" value. This means that the value is not *always* kept from the call before!
Very strange!

Did I find a bug in the conditional initialization of lexical (my) variables?
Or maybe it is just an unspecified (or 'undefined' :-)) behavior?
Which would explain that I didn't find any specific explanation in the Perldoc (Private Variables via my())?

Thank you for your help!

Matthias

Replies are listed 'Best First'.
Re: Conditional initialization of lexical (my) variables
by choroba (Cardinal) on May 10, 2023 at 14:06 UTC
    You found a bug that many people had found before and that was finally fixed in 5.30. Read for example this article on Effective Perl Programming to get the background.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      What are you suggesting is fixed in 5.30? I was not aware of any change in behaviour around this (which I understood was unfixable due to back-compat), and indeed I see the same output for the OP's script with 5.30, 5.32, 5.34 and 5.36.

        perl -wE 'BEGIN { print $] } my $x if 0' 5.037003 This use of my() in false conditional is no longer allowed at -e line +1.
        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      Thank you for your answer!
      I also noticed that I have already sent the same question before (id:11151504)!
      Shame on me! I'm getting old!

      But thanks a lot for the article link!
      The article makes things very clear! :-)

Re: Conditional initialization of lexical (my) variables
by LanX (Saint) on May 10, 2023 at 21:03 UTC
Re: Conditional initialization of lexical (my) variables
by ikegami (Patriarch) on May 11, 2023 at 08:32 UTC

    Did I find a bug in the conditional initialization of lexical (my) variables?

    No. The documentation explicitly forbids you from using a lexical var without first executing the my statement.

Re: Conditional initialization of lexical (my) variables
by Anonymous Monk on May 11, 2023 at 03:25 UTC

    I am not sure why you would expect any other behavior?

    If the "unless" condition results in the "my my_var" not executing, then 'my_var' is never declared in the scope of the subroutine.

    So when the next line assigns to 'my_var' - the assignment is done to a global variable.

    So it is not persisting across calls, its just not scoped to the subroutine, but your entire file.

    I always thought it was perfectly logical code, and I had used it in some situations as a convenient form of name overloading. Alas it looks like this caught out so many people they made it an error, and now people even claim it was a bug all along... which is nonsense.

      So when the next line assigns to 'my_var' - the assignment is done to a global variable.

      That's not what's happening. It is indeed assigning to the lexical var.

      Running my pushes a directive on the stack that clears the lexical var on scope exit. This is what's being skipped in the OP's code.

      > If the "unless" condition results in the "my my_var" not executing, then my_var is never declared in the scope of the subroutine.

      this doesn't make sense

      • declaration is a compile-time effect
      • execution is a run-time effect
      mixing both for a parent scope opens a box full of problems. That's why declaration with post-fix conditions should be avoided.

      Cheers Rolf
      (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
      Wikisyntax for the Monastery