June 9, 2024·6 min read·engineeringperlexceptionsclasseslanguage

Perl 5.40 separates stable exception handling from experimental classes

Perl 5.40 stabilizes try/catch while extending native classes with field readers and runtime class names. The distinction matters more than the shared release number.

Perl 5.40 gives try and catch stable status without making every related construct stable. The optional finally block remains experimental. Native classes also remain experimental, even as the release adds useful facilities for accessing fields and identifying the class being constructed. A single release can advance several parts of a language while making different commitments about each one.

The release history records Graham Knop's release on 9 June. The 5.40 notes compare it with 5.38.0 and identify the experimental boundaries explicitly. A useful way to read those additions is through the behaviour a program can now express and the status attached to that behaviour.

I still prefer Perl for a short, specific text-processing task. For that use, stable exception syntax has a more direct appeal than a new object system. The class work is interesting for a different reason, because it moves some conventions that programmers previously assembled themselves into interpreter-defined behaviour.

Try is ordinary control flow

try first appeared in 5.34.0, according to the release notes. In 5.40, using try and catch no longer emits an experimental warning. A program still needs to enable the feature, either explicitly or through the :5.40 feature bundle. The latter is also the bundle selected by -E in this release.

The syntax reference describes a try block followed by a catch block with its own lexical exception variable. Its handler runs when an exception escapes the try block. The variable declaration follows catch in parentheses, with no explicit my required.

use v5.40;

try {
    die "invalid record\n";
}
catch ($error) {
    warn "Record rejected: $error";
}

The important semantic difference from block eval concerns control flow. A return inside try returns from the containing function. A return inside block eval only returns from that block. Replacing an existing exception-catching expression therefore involves understanding its enclosing function, even when both forms catch the same exception.

That difference also appears in stack inspection. The syntax reference says that try does not appear in caller, because it does not create the return boundary associated with a subroutine or eval block. It behaves like other control-flow syntax in that respect. A reader can follow a return through the construct without introducing another implicit destination.

Value-producing use is possible as well. When the construct is the final statement in a function or do block, it yields its last evaluated value. That lets a handler supply a fallback expression. An explicit return in that position has the separate effect of returning from the surrounding function, so the distinction remains relevant even in a short expression.

Cleanup is a separate commitment

The feature documentation says that the form with finally still produces an experimental warning. That is easy to miss when a release headline describes exception handling as stable. Stability applies to the documented try/catch form, with the cleanup extension retaining a different status.

The syntax reference describes finally as equivalent to a defer block. It runs after the rest of the construct finishes, including when an exception or a return transfers control elsewhere. Its body cannot return from the surrounding function or use the listed controls to leave an enclosing loop. Its final expression also does not replace the function's return value.

Those rules address a different concern from catching a failure. Exception handling decides what to do when execution throws. Cleanup must also behave correctly when execution leaves normally or through another control-flow path. The release's distinct experimental status keeps those commitments visible to a program that chooses to depend on them.

Classes define instance storage in the interpreter

The class reference for 5.40 contrasts the native class feature with the traditional system of blessing references into packages. It describes a new interpreter implementation, not a wrapper around bless. Classes receive a generated new constructor, and fields have separate storage for each instance.

A field is lexically visible within the class's methods and adjustment blocks. It remains private unless the class exposes it through a method. Inheritance does not make a parent's lexical fields directly visible in the subclass. That establishes a concrete encapsulation rule instead of requiring every class to enforce a convention around an externally accessible structure.

The new :reader attribute generates an accessor that returns a field's value. For a scalar field named $name, its default method name is name. An attribute argument can choose another method name. The generated method has an empty signature, so it is a reader rather than a combined getter and setter.

use v5.40;
use feature 'class';

class Record {
    field $name :param :reader;
}

my $record = Record->new(name => 'sample');
say $record->name;

This deliberately uses the experimental class feature and can emit its warning. The field's :param attribute makes the named constructor parameter available, and without a default the parameter is required. A reader exposes the value through a method. The example does not require the caller to know the object's internal storage representation.

The class reference also documents readers for array and hash fields. Their context follows the field variable. A reader returns the contents in list context and the element count in scalar context. A generated reader therefore reduces method boilerplate without removing Perl's context-dependent interpretation of the returned expression.

The generated method does not imply that the field itself becomes public storage. Access still occurs through the method selected by the class declaration. This matters when an implementation changes, because callers can continue to ask for a value without naming the storage that contains it. The default accessor name is convenient, but the explicitly named form also lets a class keep its external vocabulary separate from its field names. A reader attribute therefore specifies a small part of the public interface while leaving the field under the class scope rules described above.

Runtime class identity matters during construction

__CLASS__ supplies another small but specific addition. The release notes distinguish it from __PACKAGE__, which names the compile-time package. Inside a method, field initializer or ADJUST block, __CLASS__ knows the object's actual runtime class.

The construction case explains why that matters. During a field initializer, the object is not yet constructed and $self is unavailable. A class name is still useful for dispatching a class method that supplies the field's default. If a subclass overrides that method, using the runtime class allows construction to select the override.

The class reference illustrates this with a base-class field whose initializer calls a default-producing method through __CLASS__. Constructing a subclass uses the subclass's version of that method. Hard-coding the base package name would ask a different question, because it would select the package where the source was written instead of the class currently being constructed.

The class feature remains incomplete and experimental. Its reference lists further accessor generation and roles among future work, which should not be read as functionality delivered by 5.40. The new reader attribute is available now, while the broader object model still carries the status documented for the whole feature.

I find the explicit storage model more compelling than saving a few lines in an accessor. Perl's traditional flexibility lets different modules choose different object conventions. A native field has a specified relationship to its instance and its lexical scope, which gives the reader more information directly from the declaration.

Other additions preserve the same distinction

The release also adds ^^, a logical exclusive-or spelling at medium precedence. The existing word xor remains available at low precedence. That addition changes how a boolean operation can group with surrounding expressions, rather than introducing exclusive-or as a new logical concept.

Another distinction appears in builtin. The module itself is now stable, but some functions remain experimental. Its versioned import bundle selects functions considered non-experimental for the named release. The documentation explains that these imports create lexical aliases, so importing a function into a class scope does not accidentally make it a package method.

These changes affect different aspects of program structure. Stable exception handling gives control flow a supported syntax. Native classes establish instance fields while continuing to evolve experimentally. Lexical utility imports reduce accidental package members. For code adopting 5.40, each declaration can express a specific choice, and each choice retains the stability status that the release actually documents.