Search code examples
perl

Destructive @_ operation and passing @_ in the same statement


I'm looking for perl docs confirming the following behaviour, where passed @_ still holds the first argument, despite shift() which alters @_.

use Data::Dumper;
sub f { shift->(@_) }

f(sub{ print Dumper \@_ }, 1,2);

output

$VAR1 = [
      sub { "DUMMY" },
      1,
      2
    ];

Solution

  • You are asking about the order in which the shift is performed relative to creation of the argument list.

    It's not documented.

    Rely on this at your own risk, but Perl always evaluates the left of the -> last in a call (so that the code reference is topmost on the stack).

    In the following, you can see that @_ (lines 3 and 4) is evaluated before shift (line 5).

    $ perl -MO=Concise,-exec,f -e'
    use Data::Dumper;
    sub f { shift->(@_) }
    f(sub{ print Dumper \@_ }, 1,2);
    '
    main::f:
    1  <;> nextstate(main 1266 -e:3) v
    2  <0> pushmark s
    3  <#> gv[*_] s
    4  <1> rv2av[t2] lKM/1
    5  <0> shift s*
    6  <1> entersub[t3] KS/TARG
    7  <1> leavesub[1 ref] K/REFC,1
    -e syntax OK
    

    Let's walk through it.

    1. Let's say that @_ initially contains scalars A, B and C.
    2. Line 2 marks the stack.
    3. Lines 3 and 4 places scalars A, B and C on the stack.
    4. Line 5 then places A on the stack. (It now contains four scalars since the mark.) It also shifts the contents of @_ so that it now contains B and C.
    5. Line 6 calls the sub identified by the topmost scalar on the stack, A. The remaining scalars added since the mark (A, B and C) are provided to the sub as arguments.

    Solution: Avoid reading and modifying the same variable in the same expression.

    sub f { $_[0]->( @_[ 1 .. $#_ ] ) }
    
    sub f { my $cb = shift; $cb->( @_ ) }
    
    sub f { my $cb = shift; &$cb }
    
    sub f { &{ +shift } }
    

    Or if you want to remove the stack frame for f from the stack (hiding it from caller and croak), you can use the following:

    sub f { my $cb = shift; goto &$cb }