April 29, 2024·4 min read·engineeringperlexceptionslanguage

Perl 5.39.10 puts try into the default modern feature bundle

The April snapshot makes try/catch available through the upcoming 5.40 bundle and -E, while finally remains experimental.

Making a language feature stable and making it available in a normal program are separate decisions in Perl. The 5.39.10 development snapshot connects them for try and extra_paired_delimiters. Both now belong to the upcoming :5.40 feature bundle. Because the command-line -E option uses the latest bundle, short programs invoked that way can use them immediately in this snapshot.

The release history dates Paul Evans's release to 29 April. The release notes describe the additions as recently stabilised features. This is a change to the defaults selected by a feature bundle, not the introduction of exception handling into a language that previously lacked it.

I find the command-line consequence especially relevant to Perl's usefulness for one-off text work. A feature that requires several lines of setup has a different cost in a small command than in a module. Including try in the bundle selected by -E makes structured exception handling easier to use in that setting.

A bundle selects language behaviour

Perl's feature pragma lets a program enable individual features or a versioned collection. The :5.40 collection includes try and the additional paired delimiters. Earlier versioned bundles preserve their own selections, so running older source on a new interpreter does not by itself mean that the source has selected every new feature.

That distinction supports gradual language evolution. An interpreter can understand a construct while a particular lexical scope has not enabled it. A versioned bundle expresses a program's chosen set of language behaviours. It also avoids making every program repeat a growing set of individual feature names.

For a source file that only needs exception handling, the explicit declaration remains available. The example below uses the documented catch-variable syntax and does not rely on the upcoming stable release's version declaration.

use strict;
use warnings;
use feature 'try';

try {
    die "input rejected\n";
}
catch ($error) {
    warn "Unable to continue: $error";
}

The exception value arrives in a lexical variable whose declaration is part of catch. There is no my in that declaration. The syntax documentation shipped with the release explains that the declaration is implicit and the variable is visible to the catch block.

Return keeps its enclosing function

The syntax documentation gives try a control-flow property that matters when extracting code from an eval block. A return inside try returns from the containing function. Inside an eval block, return instead returns from that block. Similar-looking exception-handling code can therefore have different behaviour after a rewrite.

This makes try a normal control-flow construct in the sense described by the documentation. It does not introduce a function-like return boundary. The documentation also says that try blocks do not appear in caller, unlike subroutine calls and eval blocks. Both properties fit the same model of where control can leave the program fragment.

A catch block runs when an exception escapes the associated try block. When no exception occurs, execution skips the handler. Unless a block transfers control elsewhere, execution continues after the construct. Those rules let a reader follow the normal path without treating the handler as another operation that always runs.

The construct can also provide a value when it is the final statement in a function or a do block. In that case, its last evaluated expression supplies the value. Adding return merely to make the expression look explicit would change the control flow, because it would leave the enclosing function. This is a semantic detail worth preserving in examples of value-producing exception handling.

Finally retains its experimental status

The stable status of try and catch does not extend to the optional finally block. The feature documentation explicitly preserves the experimental warning for that form. A statement that all three keywords have become stable would misdescribe the boundary chosen for this release series.

The syntax documentation describes finally in terms of defer. Its block runs after the rest of the construct finishes, including when control leaves through an exception or an explicit return. The final block cannot itself use return or transfer control out with the documented loop controls. Its expression value does not replace the containing function's return value.

Those restrictions concern cleanup semantics, which are separate from the availability of a catch handler. The April bundle change can make ordinary try/catch convenient while leaving the cleanup form subject to experimental treatment. Programs should distinguish the two when describing which experimental features they depend on.

More paired delimiters become an ordinary choice

The second addition expands the set of paired string delimiters beyond the traditional four pairs. The feature documentation says it first appeared in 5.36 and was experimental through 5.38. Its stabilisation removes the usage warning, while the old warning category remains for compatibility with code that disables it.

Non-ASCII delimiters still require attention to the encoding of the source file. The documentation recommends use utf8 when the parser needs to decode UTF-8 source containing those characters. Enabling an additional delimiter pair and decoding the source bytes are distinct requirements.

I am more interested in the exception-handling default than in adding more punctuation to short programs. The consistent principle is useful, though. Once a feature is stable, a modern bundle can make it available without forcing every program to carry its experimental setup. In this snapshot, that principle reaches both ordinary source scopes and the command-line programs selected by -E.