Perl subroutine signatures
last modified November 7, 2026
A subroutine signature is a parameter list written after the subroutine name,
as in sub add($x, $y) { ... }. Perl copies the arguments into the
named lexical variables and checks that the caller passed the right number of
them. Signatures were added as an experimental feature in Perl 5.20 and became
stable in Perl 5.36. They replace the traditional my ($x, $y) = @_;
line that starts most Perl subroutines.
The use v5.36 statement, or any later version, enables the
signatures feature together with strict,
warnings, and say, so the examples in this article
need no other pragmas. They were run with Perl 5.44 on Linux.
A first signature
The parameters are listed in parentheses between the subroutine name and its body. Each one is a lexical variable that is visible only inside the body.
#!/usr/bin/perl
use v5.36;
sub add($x, $y) {
return $x + $y;
}
sub describe($name, $age) {
return "$name is $age years old";
}
say add(3, 4);
say describe('Lucia', 23);
The add subroutine declares two parameters, $x and
$y. The call add(3, 4) copies 3 into $x
and 4 into $y. Because the values are copies, assigning to a
parameter never changes the caller's variable, unlike assigning to an element
of @_.
$ perl simple.pl 7 Lucia is 23 years old
The classic @_ style
Before signatures, a subroutine received its arguments only in the
@_ array, and the usual idiom was to unpack them with a list
assignment. This style still works, but Perl does not check the number of
arguments.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
sub add {
my ($x, $y) = @_;
return $x + $y;
}
say add(3, 4);
say add(3, 4, 5);
{
no warnings 'uninitialized';
say add(3);
}
With three arguments, the third one is silently ignored. With one argument,
$y is undef; the program prints 3 and, with warnings
enabled, an uninitialized warning. The block with
no warnings shows that without warnings the mistake goes completely
unnoticed. Both calls are bugs that the caller would want to know about.
$ perl classic.pl 7 7 Use of uninitialized value $y in addition (+) at classic.pl line 9. 3
Arity errors
A subroutine with a signature checks its arity, the number of arguments it receives. Too few or too many arguments raise an exception at run time.
#!/usr/bin/perl
use v5.36;
sub add($x, $y) {
return $x + $y;
}
for my $args ([3, 4], [3], [3, 4, 5], []) {
my $res = eval { add(@$args) };
if ($@) {
print "add(@$args): $@";
} else {
say "add(@$args) = $res";
}
}
The program calls add with lists of different lengths inside
eval and prints the error messages. The message names the
subroutine and states how many arguments it got and expected. Note that the
line number points to the call, not to the subroutine, so the error leads
straight to the faulty caller. Since the check happens at run time, a wrong call
in a branch that never runs goes unnoticed; signatures are not compile-time
type checks.
$ perl arity.pl add(3 4) = 7 add(3): Too few arguments for subroutine 'main::add' (got 1; expected 2) at arity.pl line 11. add(3 4 5): Too many arguments for subroutine 'main::add' (got 3; expected 2) at arity.pl line 11. add(): Too few arguments for subroutine 'main::add' (got 0; expected 2) at arity.pl line 11.
Default values
A parameter can have a default value, written with =. The default
is used when the caller passes fewer arguments. Parameters with defaults must
come after the mandatory ones.
#!/usr/bin/perl
use v5.36;
sub greet($name, $greeting = 'Hello') {
return "$greeting, $name!";
}
sub rectangle($width, $height = $width) {
return $width * $height;
}
my $calls = 0;
sub ticket($owner, $id = ++$calls) {
return "ticket $id for $owner";
}
say greet('Peter');
say greet('Peter', 'Good morning');
say rectangle(4, 5);
say rectangle(4);
say ticket('Anna');
say ticket('Boris');
say ticket('Clara', 100);
say ticket('Dusan');
The default can be any expression. In rectangle, it refers to an
earlier parameter, so a single argument yields a square. In
ticket, the expression ++$calls has a side effect: it
is evaluated only when the default is actually needed, which is why the call
with an explicit 100 does not advance the counter. A subroutine with optional
parameters accepts a range of arguments; greet takes one or two.
$ perl defaults.pl Hello, Peter! Good morning, Peter! 20 16 ticket 1 for Anna ticket 2 for Boris ticket 100 for Clara ticket 3 for Dusan
The //= and ||= defaults
A plain = default applies only when an argument is missing. An
explicit undef is a real argument and is kept. Perl 5.38 added the
//= and ||= forms: the first also replaces an
undef argument, the second any false argument.
#!/usr/bin/perl
use v5.36;
sub plain($name = 'guest') {
return $name // 'undef';
}
sub defined_or($name //= 'guest') {
return $name;
}
sub logical_or($count ||= 10) {
return $count;
}
say 'plain(): ', plain();
say 'plain(undef): ', plain(undef);
say 'defined_or(undef):', defined_or(undef);
say 'defined_or(""): "', defined_or(''), '"';
say 'logical_or(0): ', logical_or(0);
say 'logical_or(5): ', logical_or(5);
Calling plain(undef) returns undef, because an
argument was passed. The //= default turns undef into
guest but keeps the empty string, which is defined. The
||= default replaces every false value, so the argument 0 becomes
10; this is convenient for counts and sizes where 0 makes no sense, and wrong
where 0 is a valid value. The //= form is especially useful when a
caller passes a value that may be undef, such as a hash element
that does not exist.
$ perl undef_default.pl
plain(): guest
plain(undef): undef
defined_or(undef):guest
defined_or(""): ""
logical_or(0): 10
logical_or(5): 5
Slurpy parameters
The last parameter can be an array or a hash. Such a slurpy parameter collects all remaining arguments, and its presence removes the upper limit on the number of arguments.
#!/usr/bin/perl
use v5.36;
use List::Util qw(sum0);
sub total($label, @values) {
return sprintf "%s: %d item(s), sum %d", $label, scalar @values, sum0 @values;
}
sub connect_to($host, %opts) {
my $port = $opts{port} // 80;
my $timeout = $opts{timeout} // 30;
return "$host:$port (timeout $timeout s)";
}
say total('empty');
say total('scores', 4, 8, 15, 16, 23, 42);
say connect_to('example.com');
say connect_to('example.com', port => 443, timeout => 5);
eval { connect_to('example.com', 'port') };
print "error: $@";
In total, @values receives everything after the label,
possibly nothing. In connect_to, the remaining arguments are
assigned to %opts as key/value pairs, which gives the subroutine
named options. A slurpy hash requires an even number of remaining arguments;
the last call passes only a key, and Perl raises an Odd name/value
argument error instead of storing a key with an undef
value.
$ perl slurpy.pl empty: 0 item(s), sum 0 scores: 6 item(s), sum 108 example.com:80 (timeout 30 s) example.com:443 (timeout 5 s) error: Odd name/value argument for subroutine 'main::connect_to' at slurpy.pl line 23.
Placeholders
A parameter that the subroutine does not use can be written as a bare sigil. It still takes part in the arity check, but no variable is created.
#!/usr/bin/perl
use v5.36;
sub on_event($name, $, $data) {
say "$name: $data";
}
sub first_only($first, @) {
return $first;
}
sub label($text, $=) {
return "[$text]";
}
on_event('click', 'ignored timestamp', 'button #1');
say first_only(qw(alpha beta gamma));
say label('one');
say label('two', 'extra');
In on_event, the middle argument is required but ignored. A bare
@ in first_only accepts and discards any number of
extra arguments. The $= in label marks an optional
argument that is ignored when present. Placeholders are common in callbacks
whose arguments are dictated by the code that calls them.
$ perl placeholders.pl click: button #1 alpha [one] [two]
@_ in signatured subroutines
In a subroutine with a signature, the arguments are meant to be read from the
parameters. Using @_ there is still possible but discouraged.
#!/usr/bin/perl
use v5.36;
sub count_args($x, @rest) {
return scalar @_;
}
say count_args(1, 2, 3);
Perl emits an experimental warning when it compiles the use of
@_. The behaviour of @_ inside signatured subroutines
may change in a future version, so new code should count arguments with the
parameters instead: here, 1 + @rest.
$ perl atunder.pl Use of @_ in scalar with signatured subroutine is experimental at atunder.pl line 6. 3
Signatures in methods
A method is an ordinary subroutine that receives the invocant, the object or the class name, as its first argument. With a signature, the invocant is simply the first parameter.
#!/usr/bin/perl
package Account;
use v5.36;
sub new($class, %args) {
my $self = {
owner => $args{owner} // die("owner required\n"),
balance => $args{balance} // 0,
};
return bless $self, $class;
}
sub deposit($self, $amount) {
die "amount must be positive\n" if $amount <= 0;
$self->{balance} += $amount;
return $self;
}
sub balance($self) {
return $self->{balance};
}
package main;
use v5.36;
my $acc = Account->new(owner => 'Roman', balance => 100);
$acc->deposit(50)->deposit(25);
say $acc->balance;
eval { $acc->deposit };
print "error: $@";
The constructor takes the class name and a slurpy hash of named arguments; the
accessor balance takes only $self. The invocant counts
towards the arity, so when deposit is called without an amount,
the message reports one argument received and two expected. The
use v5.36 statement is repeated after package main
only for clarity; it is lexically scoped and the first one already covers the
whole file.
$ perl methods.pl 175 error: Too few arguments for subroutine 'Account::deposit' (got 1; expected 2) at methods.pl line 35.
Anonymous subroutines
Signatures work for anonymous subroutines as well; the parameter list follows
the sub keyword.
#!/usr/bin/perl
use v5.36;
my $multiply = sub ($x, $y) { $x * $y };
say $multiply->(6, 7);
sub make_counter($start = 0, $step = 1) {
my $n = $start;
return sub () { my $cur = $n; $n += $step; return $cur };
}
my $c = make_counter(10, 5);
say join ' ', map { $c->() } 1 .. 4;
my %ops = (
add => sub ($x, $y) { $x + $y },
sub => sub ($x, $y) { $x - $y },
pow => sub ($x, $y) { $x ** $y },
);
say "$_: ", $ops{$_}->(2, 10) for sort keys %ops;
eval { $c->(1) };
print "error: $@";
The make_counter function has two optional parameters and returns a
closure. The closure has an empty signature, (), which declares
that it takes no arguments at all; calling it with one raises an error. The
%ops hash maps operation names to small anonymous subroutines. In
error messages, an anonymous subroutine is named __ANON__.
$ perl anon.pl 42 10 15 20 25 add: 12 pow: 1024 sub: -8 error: Too many arguments for subroutine 'main::__ANON__' (got 1; expected 0) at anon.pl line 25.
Validating values
A signature checks only how many arguments arrive, not what they contain. Value checks still belong in the body.
#!/usr/bin/perl
use v5.36;
use Scalar::Util qw(looks_like_number);
sub percent($part, $whole) {
die "part is not a number\n" unless looks_like_number $part;
die "whole must be a positive number\n"
unless looks_like_number($whole) && $whole > 0;
return sprintf '%.1f %%', 100 * $part / $whole;
}
say percent(17, 40);
for my $args (['x', 40], [5, 0], [5]) {
eval { percent(@$args) };
print "error: $@";
}
The percent subroutine uses looks_like_number from
Scalar::Util to verify both arguments and refuses to divide by
zero. The arity check runs first, before any statement in the body, so the
last call fails with the signature error, not with one of the custom messages.
$ perl validate.pl 42.5 % error: part is not a number error: whole must be a positive number error: Too few arguments for subroutine 'main::percent' (got 1; expected 2) at validate.pl line 17.
Signatures and prototypes
Prototypes and signatures share the parentheses after the subroutine name, so a
prototype must be written with the :prototype attribute when a
signature is used. A prototype changes how a call is parsed; a signature
changes how the arguments are unpacked. The two can be combined.
#!/usr/bin/perl
use v5.36;
sub apply :prototype(&@) ($code, @items) {
return map { $code->($_) } @items;
}
say join ' ', apply { $_[0] * 2 } 1 .. 5;
say join ' ', apply { uc $_[0] } qw(a b c);
The &@ prototype lets apply take a bare block as
its first argument, the way map and grep do. The
signature then unpacks the block, as a code reference, into
$code and the rest of the arguments into @items.
$ perl prototype.pl 2 4 6 8 10 A B C
Best practices
- Use signatures in new code: with
use v5.36or later they are stable and enabled automatically. - Let the arity check work: do not add a slurpy
@just to silence errors; extra arguments are usually a bug in the caller. - Choose the right default:
=for missing arguments,//=whenundefshould also get the default,||=only when every false value is invalid. - Use a slurpy hash for options: named options keep long parameter lists readable and easy to extend.
- Avoid @_ in signatured subroutines: it triggers an experimental warning and its behaviour may change.
- Validate values in the body: signatures check counts, not types or ranges.
In this article we have worked with Perl subroutine signatures: mandatory and
optional parameters, //= and ||= defaults, slurpy
arrays and hashes, placeholders, arity errors, signatures in methods and
anonymous subroutines, and the :prototype attribute.
Source
Author
List all Perl tutorials.