Arguments and @_#
When a sub is called the classic way (no signature), Perl flattens the entire argument list into a single array and exposes it inside the body as @_. The elements of that array are not copies — they are aliases to the caller’s expressions. This single rule is the source of most of Perl’s calling-convention surprises and most of its calling-convention power.
The aliasing rule#
sub bump {
$_[0]++; # mutates the caller's variable
}
my $x = 10;
bump($x);
say $x; # 11
Modifying $_[0] writes through the alias to whatever the caller passed. That is by design — it is how chomp(@lines) manages to strip newlines from every element of @lines rather than from a copy of @lines.
The aliasing extends to literals, with predictable consequences:
bump(10); # error: Modification of a read-only
# value attempted
A literal 10 is a constant. The alias points at it; the mutation attempt dies.
The unpacking idiom#
Because the aliasing is rarely what the body wants, almost every classic-style sub starts with one of these two patterns:
# Named arguments (most common)
sub greet {
my ($name, $greeting) = @_; # COPY into private lexicals
$greeting //= 'Hello';
return "$greeting, $name!";
}
# Method dispatch
sub method_call {
my $self = shift; # COPY first arg, leave rest in @_
my %args = @_; # remaining args as a hash
...
}
Once unpacked, the body works with private copies and cannot accidentally mutate the caller’s state. shift with no argument inside a sub defaults to @_, which is why my $self = shift; is everywhere in OO Perl.
Legitimate uses of aliasing#
Aliasing is the right tool when you actually want to mutate caller state:
# in-place trim
sub trim_inplace {
for (@_) {
s/^\s+//;
s/\s+$//;
}
}
my @data = (" hello ", " world\n");
trim_inplace(@data);
# @data is now ("hello", "world")
The for (@_) loop aliases $_ to each element of @_, which is itself an alias to each element of @data. The s/// modifications go through both alias hops to the original strings. Without the aliasing, this same shape of code would be twice as much work.
The same mechanism powers chomp(@lines), chop(@buf), and any ”work over a list and modify in place“ function you write yourself. State the intent in the function’s name (_inplace, mutate_, similar) so callers are not surprised.
Named arguments#
Perl has no built-in named-parameter syntax in the classic form, but the => (fat comma) plus a hash assignment gives the same effect:
sub configure {
my %opt = @_; # ('host' => 'localhost', 'port' => 8080)
$opt{host} //= 'localhost';
$opt{port} //= 80;
...
}
configure( host => 'example.com', port => 8080 );
The fat comma is identical to a regular comma in semantics but quotes the bareword on its left, so host => ... is shorthand for 'host' => .... See , for the operator itself.
For mandatory-plus-optional named args, the standard shape is:
sub render {
my ($template, %opt) = @_; # one positional, rest as named
$opt{escape} //= 1;
...
}
render('greeting.tt', escape => 0, locale => 'de');
For the modern named-parameter idiom built on signatures, see signatures.
shift, pop, unshift, push on @_#
@_ is a real array. You can shift from the front, pop from the back, slice it, modify it. None of these change the caller’s state — only writing through $_[N] does.
sub describe {
my $what = shift; # remove first arg from @_
my @rest = @_; # copy what's left
...
}
sub method {
my $self = shift;
my @args = @_;
$self->dispatch(@args);
}
A handy consequence: if you want to re-pack the remaining args to forward to another sub, just write @_ — that is exactly what goto &other does without the explicit pass. See recursion for tail-call forwarding.
wantarray and the calling context#
Inside a sub, wantarray tells you the context the call was made in:
| Context | Common shape |
|---|---|---|
| list | return a list |
| scalar | return a single scalar |
| void | return early; the result will be discarded |
sub items {
return unless defined wantarray; # void: nothing to compute
my @result = compute_items();
return wantarray ? @result : scalar @result;
}
@_ says what came in; wantarray says what is expected back. They are the two halves of the calling-convention picture. See lvalue and context for the full discussion.
Common pitfalls#
my ($x) = @_vsmy $x = @_. The first is list context:$xgets the first argument. The second is scalar context:$xgets the count of arguments. The parentheses on the left are the difference; mistakes here are common.sub one_arg { my ($x) = @_; # $x = first arg } sub count_args { my $n = @_; # $n = number of args }
Modifying
$_[N]”by accident“. Functions likechomp,chop,tr///,s///operate on their argument in place.chomp($_[0])mutates the caller’s string. If that’s not what you want, copy first.Flattening of arrays and hashes. Arrays and hashes passed as arguments lose their identity at the call site — they all become elements of the single flat
@_. To pass an array without flattening, pass a reference:takes_array(\@arr); # one ref argument sub takes_array { my ($aref) = @_; push @$aref, 'extra'; }
@_aftergoto &sub. The current@_is passed through to the target. Anything you’ve already shifted off is gone from the perspective of the target. This is a feature, not a bug — see recursion.
See also#
@_— the variable’s perlvar entry, with the aliasing rule and the loop-context discussion.Signatures — the modern alternative for named-and-positional parameter handling.
Return values — what the body sends back.
Comma operator —
=>for named-arg construction.