ZetCode

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.

simple.pl
#!/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.

classic.pl
#!/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.

arity.pl
#!/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.

defaults.pl
#!/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.

undef_default.pl
#!/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.

slurpy.pl
#!/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.

placeholders.pl
#!/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.

atunder.pl
#!/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.

methods.pl
#!/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.

anon.pl
#!/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.

validate.pl
#!/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.

prototype.pl
#!/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

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

perlsub: Signatures

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.