February 20, 2022·4 min read·engineeringperlsignaturesfunctions

Perl signatures become stable with a boundary around the argument array

Perl 5.35.9 makes signatures a stable language feature. The accompanying warning for argument-array access clarifies what a signature promises.

Declaring parameters beside a subroutine name changes more than the appearance of Perl code. A signature establishes which arguments a call accepts, creates lexical variables, and rejects a call that does not meet its requirements. Perl 5.35.9 makes that facility stable after its introduction in 5.20.0 and subsequent revisions. The development release notes also mark a boundary around the older argument array, @_, when a subroutine has a signature. Stabilizing one interface does not establish every possible interaction with the other.

The history included in the release dates it to February 20. This is a development-branch decision, including signatures in the forthcoming :5.36 feature bundle. It is useful now because the documented semantics explain how to write parameter declarations without relying on behavior the project still considers experimental.

Parameter declarations perform runtime work

The subroutine manual shipped with this version describes a signature as part of the subroutine body. It runs before the braced block. Argument values populate its lexical variables, and a mismatch throws an exception before ordinary body statements execute.

use strict;
use warnings;
use feature 'signatures';

sub total ($left, $right = 0) {
    return $left + $right;
}

print total(7), "\n";
print total(7, 3), "\n";

This declaration accepts either one argument or two. A call with none fails, as does a call with three. An ordinary assignment from @_ to two lexicals does not, by itself, express those checks. The manual illustrates the equivalent explicit argument-count checks for a signature with two mandatory parameters. Reading the declaration therefore tells the reader something about accepted calls that a bare variable assignment cannot establish.

The default expression runs when its argument is absent. It does not run just because the supplied value happens to be false, and it can refer to parameters declared earlier in the signature. Evaluation happens for each applicable call, so a default can depend on changing state. That makes a default expression executable behavior worth reviewing, especially when it has side effects.

I like Perl's ability to express a text operation directly. Parameter declarations help keep that directness when the operation grows into a named subroutine. A reader can see the required input before examining the regular expressions or substitutions that use it.

Copies and aliases have different effects

Signatures give their lexical variables copies of argument values. The manual contrasts this with @_, whose elements alias the supplied arguments. Assigning to a scalar signature variable changes that local variable. Assigning through an argument-array element can change the caller's variable. These are different interfaces even when the initial values look identical.

The new warning addresses code that uses both. Explicit references to @_ or an element such as $_[0] inside a signatured subroutine provoke a compile-time warning. Implicit uses count too, including shift and pop without an explicit argument. The category is experimental::args_array_with_signatures.

That category is independent of experimental::signatures. Existing code that suppresses the old signatures warning has not thereby suppressed the new warning. More substantially, the release notes say that behavior of code attempting this combination is no longer specified and may change. Treating the warning as cosmetic would miss the project's actual compatibility statement.

The practical interpretation is to choose a subroutine's parameter interface deliberately. With a signature, named lexical variables are the documented way to consume its declared inputs. Code whose contract requires aliases through @_ needs to account for that requirement separately. Replacing a parameter-unpacking assignment with a signature is not sufficient evidence that a subroutine preserves every mutation visible to its callers.

Variable argument lists remain explicit

A trailing slurpy array parameter accepts remaining arguments without an upper argument-count limit. It must appear last. A slurpy hash instead interprets the remaining list as alternating keys and values and rejects an odd number of elements. Duplicate keys follow ordinary hash construction behavior, with the later value taking precedence.

use strict;
use warnings;
use feature 'signatures';

sub joined ($separator, @parts) {
    return join $separator, @parts;
}

print joined("/", "var", "log", "app"), "\n";

Here the separator is mandatory, while the remaining list may be empty. The declaration makes that distinction visible without separate argument-count logic. A slurpy parameter cannot have a default because an absent remainder already produces an empty array or hash. An entirely empty signature, by comparison, rejects every call that supplies arguments.

Prototypes still require separate attention. The manual explains that a prototype influences compilation of calls, while a signature binds arguments to lexical variables at runtime. When signatures are enabled, Perl does not guess which meaning an ordinary parenthesized declaration intended. A prototype can instead be supplied through the prototype attribute, before the signature.

My preference is to make that distinction visible in maintained Perl code. The stable signature facility removes the experimental warning for the parameter declarations themselves. It also leaves a precise account of what those declarations check, how they bind values, and which older argument-array interactions remain outside the stable contract.