Home / Alt manpages / perlsub(1)

  • perlsub(1)
  • User command
  • linux

Perl Subroutines: Use Signatures Without Losing @_

Every Perl subroutine still gets its arguments as one flat list in @_, signature or not, and that flattening is where copy-vs-alias bugs live. This guide writes and runs a small program that passes arguments predictably, returns values in the right context, and avoids accidental changes to the caller's variables. The examples use Perl 5.38.2 on this machine. Allow about 15 minutes if Perl is already installed.

You need a shell, /usr/bin/perl, and permission to create a temporary file in your working directory. No elevated privileges are needed. The manual page is the installed perlsub(1) from the perl-doc package; its behaviour is version-specific where this guide says so.

1. Check the interpreter version

Start by recording the interpreter that will run the examples. Perl syntax and enabled features vary between releases, especially for signatures.

$ perl -v | head -3
This is perl 5, version 38, subversion 2 (v5.38.2) built for x86_64-linux-gnu-thread-multi
(with 67 registered patches, see perl -V for more detail)
$ command -v perl
/usr/bin/perl

Checkpoint

If you are using another Perl release, run the examples first and read that release's perlsub documentation before putting signatures into shared code.

2. Start with @_ and copy the arguments

A subroutine without a signature gets one flat list. Its arguments land in the local @_ array, and the elements are aliases for the caller's scalar arguments. That aliasing is useful for a deliberate in-place update, and a common source of surprises everywhere else.

use strict;
use warnings;

sub add {
    my ($left, $right) = @_;
    return $left + $right;
}

my $total = add(2, 3);
print "total=$total\n";

Assigning to lexical variables copies the scalar values, so add cannot alter its caller through those names.

$ perl add.pl
total=5

Do not assign a whole array to @_ when you actually want to update an argument, because replacing @_ removes its aliases. If you need a deliberate mutation, make that contract obvious:

sub double_in_place {
    $_[0] *= 2;
}

my $value = 4;
double_in_place($value);
print "value=$value\n";
$ perl mutate.pl
value=8

Safety boundary

Never call an in-place routine with a literal such as double_in_place(4). Perl cannot modify a constant argument, and the call can fail at runtime.

3. Replace manual checks with a signature

Signatures name parameters and enforce their count before the body even runs. In Perl 5.38, enable them with use v5.36 or explicitly with use feature 'signatures'. This guide uses the version declaration.

use v5.36;
use strict;
use warnings;

sub add ($left, $right) {
    return $left + $right;
}

print add(2, 3), "\n";
$ perl signed.pl
5

Both parameters are mandatory here, and a call with too few or too many arguments throws an exception, which usually beats silently accepting malformed input:

$ perl -e 'use v5.36; sub add ($left, $right) { $left + $right }; print add(2), "\n"'
Too few arguments for subroutine at -e line 1.

A default makes a positional parameter optional. Defaults are evaluated when the call happens, and one can refer to an earlier parameter:

use v5.36;

sub label ($name, $prefix = "item") {
    return "$prefix:$name";
}

print label("42"), "\n";
print label("42", "record"), "\n";
$ perl defaults.pl
item:42
record:42

Keep the signature and the body close together. A default is not a type check: Perl still runs its normal scalar conversions unless your code validates the value itself.

4. Test scalar and list return context

Perl subroutines return a flat list of scalars, the caller's context can be scalar, list, or void, and return evaluates its expression in that context. This matters whenever a routine can sensibly return one summary value or several values.

use strict;
use warnings;

sub words {
    my @words = @_;
    return wantarray ? @words : scalar @words;
}

my @items = words("red", "blue");
my $count = words("red", "blue");
print "list=@items\ncount=$count\n";
$ perl context.pl
list=red blue
count=2

wantarray returns true for list context, false-but-defined for scalar context, and an undefined value for void context. If a function has no useful work to do when its return value is ignored, test defined wantarray before doing that work.

Do not expect arrays or hashes to stay separate when returned together: a return list is flat, just like the incoming argument list. Use references when the caller needs the aggregate boundaries preserved.

5. Pass arrays and hashes by reference

Passing an array directly flattens it into the argument list. Pass a reference instead when the subroutine needs one array as one argument, or may need to change the original deliberately.

use strict;
use warnings;

sub total {
    my ($values) = @_;
    my $sum = 0;
    $sum += $_ for @{$values};
    return $sum;
}

my @values = (4, 7, 9);
print total(\@values), "\n";
$ perl reference.pl
20

\@values creates an array reference and @{$values} dereferences it. The routine above only reads the referenced array; keep that read-only behaviour where you can, and if a routine mutates a referenced structure, document it and test the caller's value afterwards.

6. Diagnose calls and prototypes separately

A prototype is not a signature. Prototypes influence how Perl parses certain calls and can check or reshape arguments at compile time, but they do not give you named lexical parameters, which is what signatures are for.

The explicit ampersand call form, such as &add(2, 3), bypasses prototype checking. Writing &name with no argument list is also legal and exposes the current @_ to the called routine, an old shortcut that is easy to misread. Prefer an ordinary call or an explicit argument list in new code.

If a subroutine is called before its definition, predeclare it with sub name;, define it earlier, or import it from a module. For a runtime-selected function, use a code reference:

my $operation = sub ($number) { $number * 3 };
print $operation->(7), "\n";
$ perl code-reference.pl
21

If this fails, check the Perl version first, and whether the feature declaration is in the same scope as the signature. Then reduce the call to one argument and print the value you are passing. Do not reach for sudo: subroutine errors are language or program errors, never permission problems.

Done means

  • You confirmed the Perl interpreter and version used for the script.
  • Ordinary parameters are copied from @_ unless mutation is deliberate.
  • Signatures are enabled explicitly and reject missing or extra arguments.
  • You tested scalar and list return context where the result actually changes by context.
  • Arrays and hashes cross the boundary by reference when their identity matters.
  • You can tell a signature apart from a prototype and an explicit & call.