Search code examples
perlargumentssubroutine

Getting wrong argument value when using @_ in Perl subroutine


I encountered a strange bug while using @_ to pass a single argument to a Perl subroutine. The value passed to a subroutine changes right after entering the subroutine.

Code example:

my $my_def = 0;
print "my_def = $my_def \n";
@someResult = doSomething($my_def);

sub doSomething {
    my $def = @_;
    print "def = $def \n";
    ...
}

This returned:

> my_def = 0
> def = 1  # instead of "0"

One more strange thing is that the code worked right for several months before.

The problem was resolved, when I changed it to:

sub doSomething {
    my $def = $_[0];

Could anyone tell what could cause the problem? Are there any limitations in using @_ to pass a single argument?

Thanks!


Solution

  • You're getting the correct behaviour, although it isn't what you expected.

    A simple rule of thumb for getting local variables from the arguments in a subroutine is to always use parentheses around the variable list in the my (...) declaration:

    sub do_something
    {
        my ($def) = @_;
        ...
    }
    

    The difference is between list context and scalar context. In scalar context, all arrays return the number of elements in the array: 1 in your case. And when you wrote my $def = @_ you provided scalar context. When you used my $def = $_[0] instead you explicitly accessed element zero of the array, which is a scalar (hence the $ sigil) so it all worked again.

    In the general case you might have:

    sub do_something_else
    {
        my ($arg1, $arg2, $arg3, @the_rest) = @_;
        ...
    }
    

    Now you have three scalar local variables, $arg1, $arg2, and $arg3, and one array, @the_rest that collects any extra arguments passed.