Skip to content
VLSI Mentor

Perl Part I · Module 10 · Subroutines and Arguments

Changing @_ Changes the Caller's Variables

Perl does not copy arguments into a subroutine. The elements of @_ are aliases for whatever the caller passed, which means a subroutine can write to its caller's variables without being handed a reference and without the call site showing anything unusual. Used deliberately that gives you an in-place transform with no return value to assign — the shortest way to normalise a list of fields. Used by accident it is action at a distance of the worst kind, because the evidence is in one file and the effect is in another. The habit that eliminates it entirely is one line long and is the same line Chapter 10.3 recommended for a completely different reason.

Advanced14 min readPerlPart ISubroutines

Part I · Module 10 · Chapter 10.4 · Changing @_ Changes the Caller's Variables

1. One Assignment, Two Scopes

Azvya Education Pvt. Ltd.VLSI Mentor
alias-caller.pl
use strict;
use warnings;

sub clobber { $_[0] = "CHANGED"; return }

my $value = "original";
clobber($value);

print "the caller's variable is now: $value\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
the caller's variable is now: CHANGED
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

$value is a my variable in the caller's scope. The subroutine was not given a reference to it, the call site is an ordinary clobber($value), and the value changed.

$_[0] is not a copy of the first argument. It is another name for it — an alias, exactly as Chapter 9.2 measured for a foreach loop variable. Assigning to the alias assigns to the original.

That is Perl's actual calling convention, and it is worth stating plainly: Perl passes arguments by alias, not by value. Most languages copy; Perl does not, and then gives you a one-line idiom to copy yourself.

2. The Fix Is the First Line

Azvya Education Pvt. Ltd.VLSI Mentor
alias-safe.pl
use strict;
use warnings;

sub safe {
    my ($x) = @_;          # copy FIRST
    $x = "CHANGED";
    return;
}

my $value = "original";
safe($value);

print "with my (\$x) = \@_ : $value\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
with my ($x) = @_ : original

my ($x) = @_; copies the values out of the aliases into fresh lexical variables. Everything after that line operates on copies, so the caller is unreachable.

Chapter 10.3 recommended that same line for readability — one line that names the whole interface. This is the stronger reason. The two arguments together make it close to unconditional:

Unpack on the first line, then never mention @_ again.

The exceptions are narrow and deliberate: an arity check on scalar(@_), a subroutine whose interface genuinely is a variable-length list (Chapter 10.2 §5), and §4's in-place transforms.

3. What the Alias Can Point At

The alias points at whatever the caller actually passed, and not everything is writable.

Azvya Education Pvt. Ltd.VLSI Mentor
alias-kinds.pl
use strict;
use warnings;

sub bump { $_[0] .= "!"; return }

my @a = ("x");
bump($a[0]);
printf "array element : [%s]\n", $a[0];

my %h = (k => "y");
bump($h{k});
printf "hash value    : [%s]\n", $h{k};

my $ok = eval { bump("literal"); 1 };
print $ok ? "literal       : written\n" : "literal       : FATAL - $@";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
array element : [x!]
hash value    : [y!]
literal       : FATAL - Modification of a read-only value attempted at alias-kinds.pl line 4.
Caller passedWriting through $_[0]
a my scalarchanges it
an array elementchanges it — [x!]
a hash valuechanges it — [y!]
a literalfatal — Modification of a read-only value attempted
the result of an expressionfatal, for the same reason

That is the third place this curriculum has met the same read-only rule: Chapter 7.2 §5 for a reference to a literal, Chapter 9.2 §3 for a foreach over a literal list, and now an argument. The rule is about writing to something with no storage, not about any one construct.

The practical consequence mirrors Chapter 9.2's mixed-list finding: a subroutine that writes through @_ works when callers pass variables and dies when one passes a constant.

Azvya Education Pvt. Ltd.VLSI Mentor
The same subroutine, two callers
bump($field);        # fine
bump("default");     # fatal

A caller passing a literal is an entirely reasonable thing to do, and it is the call that fails — so the failure arrives from a caller that looks simpler than the ones that work.

4. Deliberate In-Place Transforms

The aliasing is not only a hazard; it is the mechanism behind a genuinely useful shape.

Azvya Education Pvt. Ltd.VLSI Mentor
alias-deliberate.pl
use strict;
use warnings;

sub trim_all {
    for (@_) { s/^\s+|\s+$//g }
    return;
}

my @fields = ("  alu  ", " fifo", "regf  ");
trim_all(@fields);

printf "trimmed in place: [%s]\n", join("|", @fields);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
trimmed in place: [alu|fifo|regf]

One subroutine call normalised the whole array, with no return value to assign and no reference to take.

Read how that works, because two separate aliasing rules are stacked:

  1. @_ aliases the caller's @fields elements — this chapter's rule.
  2. for (@_) aliases $_ to each element of @_ — Chapter 9.7's rule.

So s/// on $_ reaches through both layers to $fields[0]. Neither layer copies, which is why the substitution lands on the original.

This is a legitimate Perl idiom and chomp works the same way — chomp(@lines) modifies @lines in place, and so does chomp($line). But adopt it with two conditions:

  • The name must say so. trim_all, normalise_in_place, chomp_all — a verb that implies mutation. A subroutine called trimmed that modifies its argument is misnamed.
  • Document that literals are not allowed, because §3 measured that a literal argument is fatal.

The alternative, which is almost always better for anything shared, is to take a reference:

Azvya Education Pvt. Ltd.VLSI Mentor
Explicit, and it accepts literals safely
sub trim_all_ref {
    my ($aref) = @_;
    for my $f (@$aref) { $f =~ s/^\s+|\s+$//g }
    return;
}

trim_all_ref(\@fields);

Now the call site shows the \, the subroutine cannot accidentally reach anything it was not given, and a caller who wants to keep the original can pass [ @fields ] — Chapter 7.5 §2's copy.

5. What Aliasing Does Not Do

Two things worth ruling out, because both are plausible guesses.

Azvya Education Pvt. Ltd.VLSI Mentor
alias-count.pl
use strict;
use warnings;

sub count_only { return scalar(@_) }

my @a = (1, 2, 3);
my $n = count_only(@a);

printf "passed \@a (3 items), sub saw %d, \@a still has %d\n", $n, scalar(@a);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
passed @a (3 items), sub saw 3, @a still has 3

Passing an array does not consume it. The subroutine saw three elements and @a still has three. Aliasing shares the elements; it does not move them. (A shift inside the subroutine consumes @_, not the caller's array — Chapter 10.2 §3.)

And the alias is to the elements, not to the array itself:

Azvya Education Pvt. Ltd.VLSI Mentor
You cannot resize the caller's array through @_
sub grow { push @_, "extra"; return scalar(@_) }

my @a = (1, 2);
my $n = grow(@a);
# $n is 3, and @a still has 2

push @_ lengthens @_, which is a real array of aliases — but @_ is not @a, so @a is unaffected. Writing to an existing element reaches the caller; adding or removing elements does not. To resize a caller's array you need a reference, which is Chapter 10.8's subject.

6. Why This Matters in Engineering Work

It is the one Perl calling-convention surprise that reaches across files. Everything else in this module is local to a subroutine; this is not. A helper in a shared library that writes to $_[0] modifies variables in every script that calls it, and the scripts' authors have no way to know from the call.

The copy-first line is therefore a library-hygiene rule, not a style preference. For anything other scripts call, my (...) = @_; on the first line is the difference between a subroutine with a contract and one with side effects nobody documented.

And the literal case decides where it blows up. A subroutine that writes through @_ is fine until someone calls it with a constant — trim_all("default") — and that caller is often the simplest one in the codebase.

When mutation is the point, make it visible. normalise(\@rows) costs one backslash and tells every future reader what to expect.

7. Common Mistakes

Using $_[0] in the body instead of unpacking. A later edit that assigns to it reaches the caller.

Assuming arguments are copied. They are aliases. Perl passes by alias.

A subroutine that modifies without saying so in its name. trim(@fields) that mutates is a trap; trim_all or a reference is not.

Calling an @_-mutating subroutine with a literal. Fatal: Modification of a read-only value attempted.

Expecting push @_ to grow the caller's array. It grows @_ only. Pass a reference.

Expecting a passed array to be consumed. It is not — measured, three elements before and after.

Relying on aliasing to return a second value. Return a list or a reference instead; Chapter 10.8 covers it.

8. Exercise

Exercise 1 — the alias. Write sub clobber { $_[0] = "CHANGED" }, call it with a my scalar, and print that scalar afterwards. Confirm STDERR is empty and the exit status is 0.

Exercise 2 — the fix. Add my ($x) = @_; as the first line and assign to $x instead. Confirm the caller's variable is untouched.

Exercise 3 — what it points at. Write sub bump { $_[0] .= "!" } and call it with an array element, a hash value, and a literal. Record all three results including the exact error.

Exercise 4 — the two callers. Write a subroutine that writes through $_[0] and call it twice: once with a variable, once with a string constant. Record which call failed and say which of the two looks simpler at the call site.

Exercise 5 — stacked aliasing. Write trim_all from §4 and trim a three-element array in place. Then explain, in two sentences, the two aliasing layers that make s/// on $_ reach $fields[0].

Exercise 6 — the explicit version. Rewrite trim_all to take an array reference. Call it with \@fields, then with [ @fields ], and report which one left the original unchanged.

Exercise 7 — not consumed. Pass a three-element array to a subroutine that only counts. Print the count and the array's length afterwards. Then add a shift inside the subroutine and print both again.

Exercise 8 — resizing. Write sub grow { push @_, "extra"; return scalar(@_) } and call it with a two-element array. Print the return value and the array's length. Explain the difference in one sentence.

9. Summary

  • Perl passes arguments by alias, not by value. $_[0] is another name for what the caller passed.
  • Writing to $_[0] changes the caller's variable — measured, a my scalar in the caller's scope became CHANGED, with empty STDERR and exit 0.
  • my (...) = @_; on the first line copies, so nothing after it can reach the caller. That is the second and stronger reason for Chapter 10.3's habit.
  • The call site shows nothing. normalise(@fields) is indistinguishable from a read-only call; normalise(\@fields) is not.
  • The alias can point at an array element or a hash value — both writable — or a literal, which is fatal: Modification of a read-only value attempted.
  • So an @_-mutating subroutine dies when a caller passes a constant, which is often the simplest caller.
  • In-place transforms stack two aliasing rules: @_ aliases the caller's elements, and for (@_) aliases $_ to those. chomp(@lines) works this way.
  • Passing an array does not consume it, and push @_ does not resize the caller's array — only element writes reach through.
  • If mutation is the point, take a reference and name the subroutine for it.

Chapter 10.5 takes the positional argument list apart: passing name-and-value pairs so the caller can give arguments in any order and skip the optional ones.

Where this fits

Part of the Perl curriculum.