Skip to content
VLSI Mentor

Perl Part I · Module 10 · Subroutines and Arguments

@_ : How Perl Passes Arguments

Chapter 10.1 established that a Perl subroutine declares no parameters. This is where the arguments actually go: a single array named @_, which every subroutine has, and which contains everything the caller passed — flattened into one list, with no record of how it was shaped on the way in. Two separate arrays arrive as one run of elements with no boundary between them. A hash arrives as its keys and values interleaved. And because @_ is just an array, nothing anywhere compares its length against what the subroutine expected, so calling a two-argument subroutine with five arguments is not an error, not a warning, and not visible from either side.

Foundation15 min readPerlPart ISubroutines

Part I · Module 10 · Chapter 10.2 · @_ : How Perl Passes Arguments

1. One Flat Array, Whatever You Passed

Azvya Education Pvt. Ltd.VLSI Mentor
flat.pl
use strict;
use warnings;

sub count { return scalar(@_) }

my @a = (1, 2);
my %h = (k => 1, j => 2);

printf "count(1, 2, 3)  = %d\n", count(1, 2, 3);
printf "count(\@a)       = %d\n", count(@a);
printf "count(\@a, \@a)   = %d   <- two arrays arrive flat\n", count(@a, @a);
printf "count(\%%h)       = %d   <- a hash arrives as pairs\n", count(%h);
printf "count()         = %d\n", count();
printf "count(1 .. 5)   = %d   <- no error, whatever the sub expects\n", count(1 .. 5);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
count(1, 2, 3)  = 3
count(@a)       = 2
count(@a, @a)   = 4   <- two arrays arrive flat
count(%h)       = 4   <- a hash arrives as pairs
count()         = 0
count(1 .. 5)   = 5   <- no error, whatever the sub expects
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

Four facts in one run:

  • @_ is one flat list. count(@a, @a) passed two two-element arrays and the subroutine saw four elements with nothing marking where the first ended. That is Chapter 5.12's rule arriving at a call boundary.
  • A hash arrives as its pairs. count(%h) gave 4 — two keys and two values, interleaved, in an order that Chapter 6.5 established is not predictable. That is the mechanism Chapter 10.5 is built on.
  • An empty call gives an empty @_, not an error.
  • There is no arity check. count(1 .. 5) passed five arguments to a subroutine, and would have done so had the subroutine expected two. Nothing on STDERR, exit 0.

The shape, drawn

Three differently-shaped calls, one flat argument array

Diagram showing three differently shaped subroutine calls converging into one flat argument array, with no arity check as the consequencef(1, 2, 3)three scalarsf(@a, @b)two arraysf(%h)one hash@_one flat listNo arity checka bad call is legal12
Whatever the caller writes — separate scalars, arrays, a hash — the subroutine receives a single flat list in @_. The shape of the call is not recoverable from inside, and because @_ is an ordinary array nothing compares its length against what the subroutine expected.

The three arrows converging is the whole point. The shape of the call is gone by the time the subroutine runs, which is why a subroutine that wants two lists cannot simply take two lists — Chapter 10.8 covers the reference-passing that solves it, and it is the same problem in the other direction.

2. Reaching the Arguments

Azvya Education Pvt. Ltd.VLSI Mentor
indices.pl
use strict;
use warnings;

sub inspect {
    printf "  count       : %d\n", scalar(@_);
    printf "  last index  : %d\n", $#_;
    printf "  first       : %s\n", $_[0];
    printf "  last        : %s\n", $_[-1];
    printf "  all         : %s\n", join(" ", @_);
    printf "  a slice     : %s\n", join(" ", @_[0, 1]);
    return;
}

inspect("alu", "fifo", "regf");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
  count       : 3
  last index  : 2
  first       : alu
  last        : regf
  all         : alu fifo regf
  a slice     : alu fifo

@_ is an ordinary array, so everything from Module 5 applies: scalar(@_) is the count, $#_ the last index, $_[0] the first element, negative indices work, and slices work. Note $_[0] uses brackets — it is an element of @_ and has nothing to do with $_, which is Chapter 9.7's entirely separate variable. Two different variables whose names look alike is a recurring Perl hazard, and this is the pair most often confused.

Reaching into @_ directly is not how you should write subroutines. Chapter 10.3 covers unpacking into named variables on the first line, and Chapter 10.4 gives the second reason why $_[0] in a body is a smell. The reason to know these forms is to read code that uses them, and for the one case where @_ as a whole is the right thing — a subroutine that genuinely takes a variable-length list.

3. shift Defaults to @_

Azvya Education Pvt. Ltd.VLSI Mentor
shift-default.pl
use strict;
use warnings;

sub first_of {
    my $first = shift;              # shift defaults to @_ inside a sub
    return "first=$first, remaining=" . scalar(@_);
}

print first_of("a", "b", "c"), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
first=a, remaining=2

Inside a subroutine, shift with no argument means shift @_. That is why my $self = shift; is the first line of nearly every object method in Perl, and why my $path = shift; is a common opening for a one-argument helper.

Outside a subroutine, bare shift defaults to @ARGV — the command-line arguments — which is a different array entirely. Measured on an empty @ARGV it returns undef. That asymmetry is deliberate and occasionally confusing: the same statement means different things depending on whether it is inside a sub.

Whereshift means
inside a subroutineshift @_
at file scopeshift @ARGV

shift also consumes — each call removes an element, so @_ shrinks as you go. That is fine when you take all the arguments, and a trap when you later add code that expects @_ to still be intact. Chapter 10.3 compares it against the list-assignment form, which does not consume.

4. Nothing Checks the Count

5. When @_ as a Whole Is the Right Thing

Not every subroutine should unpack. Three shapes where @_ is the interface:

Azvya Education Pvt. Ltd.VLSI Mentor
varargs.pl
use strict;
use warnings;

sub total     { my $s = 0; $s += $_ for @_; return $s }
sub longest   { my $l = ""; for (@_) { $l = $_ if length($_) > length($l) } return $l }
sub log_lines { print "  [$_]\n" for @_; return scalar(@_) }

printf "total(1,2,3,4)        = %d\n", total(1, 2, 3, 4);
printf "longest(alu,fifo,rf)  = %s\n", longest("alu", "fifo", "rf");
printf "log_lines printed %d line(s)\n", log_lines("a", "b");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
total(1,2,3,4)        = 10
longest(alu,fifo,rf)  = fifo
  [a]
  [b]
log_lines printed 2 line(s)

Each takes "as many as you like, all the same kind", so naming them is impossible and unpacking would be wrong. Note the output order in that run: printf evaluated log_lines(...) — which printed its two lines — before printing its own format string. Argument evaluation happens first, which is ordinary and worth seeing once.

The rule for choosing:

Use @_ directlyUnpack into names
a variable-length list of one kinda fixed set of distinct parameters
sum, max, join-like helpersanything where position means something
the subroutine forwards @_ onwardmore than about two arguments

"Position means something" is the deciding test. total(@values) has no positions; slack_of($block, $corner) is entirely positions, and positions want names.

6. Why This Matters in Engineering Work

Flattening at the call boundary is the reason references exist. A subroutine that needs two lists — the failing blocks and the waived ones — cannot take two lists, because they arrive as one. Chapter 7.2 is the fix and Chapter 10.8 is the same problem on the way back out.

A hash flattening to pairs is a feature, not a problem. It is exactly what makes report(corner => "ss", limit => 5) work, which is Chapter 10.5's pattern and the right interface once a subroutine has more than two or three arguments.

And the missing arity check is a real maintenance hazard. Adding a parameter to a subroutine does not break its existing callers — they keep compiling and keep running, passing one argument short, with the new parameter silently undef. Nothing in the language will tell you; only a defined check inside the subroutine or a signature will.

7. Common Mistakes

Expecting two arrays to arrive as two arrays. They arrive flat. Pass references.

Expecting an arity error. There is none — too few leaves undef, too many is discarded silently.

Confusing $_[0] with $_. The first is an element of @_; the second is the implicit variable from Chapter 9.7.

Using bare shift at file scope expecting @_. It takes from @ARGV there.

Forgetting that shift consumes. @_ is shorter after each call.

Writing $_[0] throughout the body. Unpack on the first line — Chapter 10.3 — and see Chapter 10.4 for the second reason.

Adding a parameter and assuming callers will break. They will not; they will pass undef.

Unpacking a genuinely variable-length list. sum, max and friends want @_ whole.

8. Exercise

Exercise 1 — flatness. Write sub count { scalar(@_) } and call it with three scalars, one two-element array, the same array twice, a two-key hash, nothing, and a five-element range. Record all six counts and explain the third and fourth.

Exercise 2 — the element forms. Write a subroutine that prints scalar(@_), $#_, $_[0], $_[-1], all of @_ joined, and a two-element slice. Call it with three arguments and confirm each line.

Exercise 3 — $_[0] against $_. Inside a subroutine, set $_ = "outer" before the call and print both $_ and $_[0]. Confirm they are unrelated.

Exercise 4 — shift. Write a subroutine that shifts one argument and reports what is left. Then put a bare shift at file scope and record what it returns with an empty @ARGV.

Exercise 5 — shift consumes. Shift two arguments and then print scalar(@_). Compare with a version using my ($a, $b) = @_; and print scalar(@_) after that.

Exercise 6 — the arity hole. Write a two-parameter subroutine and call it with one, two and three arguments. Record each result, every line of STDERR, and the exit status. Say which of the two wrong calls was harder to notice.

Exercise 7 — the three checks. Add each of §4's three checks in turn to that subroutine. For each, call it with one argument and record what happens. For the signature version, record the experimental warning too.

Exercise 8 — when not to unpack. Write total, longest and log_lines from §5. Then try to rewrite total with named parameters and say in one sentence why it does not work.

9. Summary

  • Every argument arrives in one flat array, @_. The shape of the call is not recoverable from inside.
  • Two arrays arrive as one run of elements — measured, count(@a, @a) is 4. A hash arrives as its pairs — count(%h) is 4.
  • @_ is an ordinary array: scalar(@_), $#_, $_[0], $_[-1] and slices all work.
  • $_[0] and $_ are unrelated variables.
  • shift inside a subroutine defaults to @_; at file scope it defaults to @ARGV. It consumes.
  • Nothing checks the argument count. Too few leaves the rest undef; too many is discarded in silence — measured, exit 0 and a result identical to the correct call.
  • Checks are your own work: count @_, test defined on what is required, or use a signature — which is the only form where Perl itself raises the error.
  • Adding a parameter does not break existing callers. They pass undef instead.
  • Use @_ whole for a variable-length list of one kind; unpack whenever position means something.

Chapter 10.3 is that unpacking: naming the parameters on the first line, supplying defaults, and checking what you were actually given.

Where this fits

Part of the Perl curriculum.