July 15, 2026·6 min read·engineeringperlsignatureslanguage

Perl 5.44 gives named arguments a signature contract

Perl 5.44 adds experimental named parameters to signatures. Required names, defaults, and handling of unknown arguments now have language-defined behavior, with some important constraints when positional parameters are also present.

Perl 5.44 was released today, July 15, as recorded in the interpreter history. Its release notes introduce experimental named parameters in subroutine signatures. Callers supply name and value pairs, and the signature associates those names with lexical variables before entering the function body. That moves part of argument validation into a declaration that a reader can inspect.

I have used Perl for nearly thirty years and still prefer it for one-off text transformations. For that kind of work, convenience matters, but so does seeing what a function expects without reading its argument-processing code. Named signature parameters interest me because the declaration can specify which options exist and which values the caller must provide. I would still treat the experimental status as part of any adoption decision.

The release also supports Unicode 17 and lets multi-variable foreach loops use reference aliases. Those changes have their own constraints. The signatures change is the one I would examine first for an interface that has accumulated several options, because it changes where the interface expresses its requirements.

Names participate in argument checking

The 5.44 subroutine documentation describes named parameters with a colon before the variable sigil. That colon belongs to the declaration syntax. Inside the body, the variable has its ordinary name. A parameter without a default is required, and an omitted required name produces an exception.

use v5.44;

sub format_record (:$name, :$separator = ",") {
    return join($separator, $name, "ready");
}

say format_record(name => "sample");
say format_record(separator => "|", name => "sample");

This example deliberately leaves the experimental warning enabled. The documentation identifies its category as experimental::signature_named_parameters and warns that behavior may change in future releases. An application choosing the feature should make that choice consciously. Silencing the warning does not change the stability commitment of the language feature.

The order of the pairs at the call site does not determine which variable receives a value. The name performs that association. In the example, changing the order of separator and name preserves their roles. I find that useful for options whose meaning would be hard to infer from a sequence of scalar values alone.

The declaration also provides an immediate distinction between a required value and an optional one. Omitting name fails the signature requirements. Omitting separator uses the declared default. I would prefer that distinction to be visible at the function boundary when it describes the actual interface, while leaving validation of the values themselves in ordinary code.

A required parameter does not imply that every supplied value is acceptable to the application. The signature establishes argument presence and association. If a name must be nonempty or a separator must satisfy a format constraint, I would add and test that rule explicitly. The release does not claim that named parameters provide a type system or domain validation.

Defaults have distinct meanings

Named parameters support the same defaulting operators as positional signature parameters. With =, the default expression applies when the argument is omitted. The //= form also applies it when the caller supplies undef. The ||= form applies it to false values as well. Those choices produce different interfaces, so I would select them according to the intended meaning of the option.

For a limit parameter, a supplied zero might be a meaningful request. Choosing a false-value default could replace that request with another value. For a label, an empty string might likewise be intentional. I would write examples for those cases before selecting the operator, because a convenient default is still observable application behavior.

The documentation says default expressions run as needed from left to right. An expression can use the value assigned to an earlier parameter. That permits a default to depend on another argument, but it also makes declaration order relevant even though callers can reorder their named pairs. The caller order and default-evaluation order answer different questions.

I would keep such dependent defaults simple. A declaration that performs substantial work can make an interface difficult to inspect despite its named parameters. If initialization needs several decisions or has consequential side effects, I would prefer explicit body code and tests that describe those decisions. The signature should make the ordinary call requirements easier to understand.

Mixing positional and named parameters has a constraint

A signature can declare positional parameters first and named parameters afterwards. Once named parameters appear, further positional declarations are not allowed. More subtly, a caller supplying named arguments must fill the positional slots first, even when a positional parameter has a default.

use v5.44;

sub describe_record ($kind, :$name, :$active = 1) {
    return join(" ", $kind, $name, $active);
}

say describe_record("event", name => "sample");

Here the positional argument supplies kind, and the remaining pairs supply the named options. I would use this mixed form when the leading argument has an obvious, stable role. If callers repeatedly need placeholders to reach named options, I would reconsider whether the interface should use named parameters throughout.

There is another restriction on declarations with optional positional parameters. When any positional parameter is optional, all named parameters must also be optional. Without optional positional parameters, required and optional named parameters can be mixed. That rule is easy to miss if a reader expects the named part to behave independently of the positional part.

For a migration, I would write down the existing accepted calls before changing the signature. Adding declarative validation can reject calls that procedural argument handling previously tolerated. That may be the intended improvement, but it is still a compatibility decision. Tests should cover omission and explicitly supplied values in addition to the ordinary successful call.

Unknown names need an explicit policy

Without a slurpy parameter, a name that does not match a declared named parameter causes an exception. Adding a final slurpy array or hash collects the unrecognized names and their values instead. That gives an interface a choice between rejecting unknown options and passing them into another part of the implementation.

I would normally want a small function to reject an unknown option. A misspelled name should produce a useful failure near the call boundary. Collecting everything into a remainder can be appropriate for an adapter, but the adapter then needs to decide which additional names it accepts and where they go. The permissive form transfers validation responsibility back into application code.

The documentation requires a slurpy parameter to appear last. It can follow positional and named parameters and cannot have a default. A slurpy hash interprets remaining arguments as key and value pairs and rejects an odd count. Those rules make it important to test malformed calls when the remainder is part of a public interface.

This is where I would resist adopting syntax only for a shorter implementation. A signature is valuable when it states the interface more accurately. If adding a slurpy remainder merely preserves undocumented accidental behavior, the migration has left the original uncertainty in place. I would decide which behavior is intentional before translating it into the new declaration.

A test of an unknown option should also check that the function body did not perform its ordinary work. Signature processing runs before that body, so rejection belongs at that boundary. I would make the distinction observable in a test fixture rather than inferring it only from an exception message. If the function forwards extra options, its tests instead need to establish which names the remainder contains and how the next operation validates them.

Other release changes affect text and iteration

The 5.44 notes say Unicode rules are now fully enforced for identifiers and regular-expression group names. Some continuation characters that matched \w but did not satisfy the intended Unicode identifier properties are now forbidden. The notes limit the affected source to programs using utf8 and to later positions in names. That is a separate compatibility check for code using non-ASCII identifiers or named captures.

The loop change combines two existing facilities. Multi-variable iteration can now use reference aliases among its iteration variables. The release example enables both refaliasing and declared_refs, and the notes identify both as experimental. I would inspect aliasing behavior before using that combination in a transformation that mutates its inputs, since a reference alias has a different purpose from an independent local value.

For a first use of named parameters, I would choose a small internal function with a finite option set and tests for invalid calls. I would retain the experimental warning during evaluation and document the minimum interpreter requirement with the code. The first acceptance question would be whether the signature expresses the intended contract, including omission and unknown names, more clearly than the argument handling it replaces.