July 2, 2023·6 min read·engineeringperlobjectsinterpreterlanguage-design

Perl 5.38 makes private instance fields part of the interpreter

Perl 5.38 includes experimental native classes with lexical fields, generated constructors, and a new object representation. The implementation explains both the useful guarantees and the current limits.

Perl's object flexibility has long depended on blessing a reference and treating a package's subroutines as methods. The new native class implementation moves knowledge of an object's fields into the interpreter. A field has lexical visibility, but each instance supplies its own value. That changes the relationship between source scope and runtime storage without asking a class author to maintain a hash of public-looking keys.

The release history dates Perl 5.38.0 to July 2. Its release notes include the new class syntax in a stable interpreter distribution, while explicitly retaining its experimental status. These are separate commitments. The release is available as a stable version, but the class facility remains subject to further alteration.

I still favor Perl for short text work, where native matching and substitution save ceremony. The object change matters to me because longer programs need stronger ways to describe the state behind their methods. The useful question is what the interpreter now knows and can enforce, rather than how closely a declaration resembles another language.

A class supplies structure at compile time

The class reference describes class as a native implementation, not a wrapper around bless. A declaration supplies a package intended to be a class and gives it a generated new constructor. Authors declare fields and methods within that class instead of writing the constructor themselves.

The implementation still uses familiar interpreter machinery where appropriate. The class internals documentation explains that a class is a package with additional metadata attached to its symbol-table representation. That metadata includes field descriptions and the mapping from constructor parameter names to field indexes. It also records initialization work and the superclass, if present.

The parser seals the class when it finishes its definition. The internals document says changes to the class under compilation must happen before that sealing step. Once sealed, the class cannot be modified through those construction APIs. The implementation can therefore finish organizing its field layout before objects need that layout.

This is a specific constraint with a specific benefit. Knowing the fields after compilation lets the interpreter allocate an instance's field storage with a fixed size. The new declaration is consequently more than an alternate spelling for code that constructs a blessed hash.

Fields use instance storage with lexical names

The user-facing rule is that fields are private to their declaring class. Methods and ADJUST blocks in that class can name them directly. Other callers need an accessor method if the class wants to expose a value. The name has the familiar sigil and lexical scope, while its storage depends on the current receiver.

The internals documentation describes a new object type with an array of pointers holding the individual field values. Each field has an index into that array. Array and hash fields store their corresponding internal containers directly, without an additional intervening reference. This is the documented representation, not a benchmark claim about how much faster every object operation becomes.

The distinction also affects introspection. blessed returns the class name, as it does for other blessed objects. reftype returns OBJECT for these instances. Code that assumes every object is a hash because it is blessed would therefore be relying on a representation that this implementation does not provide.

A short class makes the public interface and private storage visible together:

use v5.38;
use feature 'class';
no warnings 'experimental::class';

class Reading {
    field $name :param;
    field $value :param = 0;

    method name { return $name; }
    method value { return $value; }
    method replace($next) { $value = $next; }
}

my $reading = Reading->new(name => 'temperature');
$reading->replace(21);
say $reading->name;
say $reading->value;

This example uses explicit accessors. The reference's discussion of generated reader and writer attributes appears under unfinished work, so those attributes should not be inferred from the availability of field itself. The implementation supplies private storage, and the class supplies the methods through which callers use it.

Constructor parameters preserve distinctions between values

A scalar field marked :param obtains a named argument from new. By default the argument name is the field name without the dollar sign. The attribute can specify another name, allowing the class to distinguish its internal variable name from its constructor interface.

A parameter without a default is required. In the example, omitting name raises an exception. The default for value applies when the caller leaves that parameter out. Passing an explicit zero is not the same operation as omitting an argument, even though the resulting field value can be equal.

The class syntax also supports default expressions with //= and ||=. The first applies to an omitted or undefined value, and the second also applies to false values. Choosing between them determines whether an empty string or zero survives construction. This is useful precisely because Perl's distinctions between absent, undefined, and false are observable in application behavior.

Field initializers run during each object's construction. They can use fields that have already been set and other visible lexical variables. A lexical sequence counter can therefore supply different initial values to separate instances, while an ordinary constant initializer gives each instance its own initial state. Sharing a declaration does not mean sharing a field's value.

Methods have an implicit receiver

A method declaration creates the lexical $self automatically. Its signature covers the explicit arguments, with the receiver omitted from the signature list. The class reference says methods act as if signatures are enabled. The replace method above therefore names only the new value, while its field access refers to the receiving object.

Anonymous methods are supported too. The distinction between a method and an ordinary subroutine therefore extends beyond whether the declaration has a name. Internally, methods retain the usual subroutine representation with a marker identifying their method status. The documentation does not describe every method as a separate new kind of callable storage.

I find the combination of lexical field names and an implicit receiver more useful than shortening the method header alone. It lets a method's body state which private value it changes without spelling out the object's representation at each access. The field declaration still supplies the ownership information a reader needs.

Inheritance reuses methods without opening field scope

The class attribute :isa supplies one superclass. Inherited methods remain callable, but a subclass cannot name its parent's fields directly. Those fields are lexical to the parent. The subclass can use parent methods to operate on that state, preserving the declared boundary between representation and behavior.

The internals document explains how field indexes accommodate inheritance. A subclass's locally declared field list can begin at its own first entry while the actual field index begins after the parent's fields. The constructor parameter mapping includes inherited mappings as well. The implementation keeps track of a complete instance layout without making all fields visible in every class's lexical scope.

The superclass attribute can require a minimum version. It attempts to load the named module when necessary, and a version that is too low fails at compile time. That gives the inheritance declaration responsibility for both identifying the parent and stating the required parent interface version.

Initialization has an ordered adjustment phase

ADJUST blocks run user-defined initialization code during construction, in declaration order. They have access to $self and fields created up to that point. This provides a place for setup logic while leaving new under the native class implementation's control.

The documentation also exposes an implementation limit. Each adjustment block currently has its own callable body and invocation overhead. Combining those blocks more efficiently is listed as future work. That is a useful example of how a facility can have defined user-visible behavior while its implementation remains open to refinement.

Parameters to ADJUST blocks are also unfinished work. The reference shows possible syntax in its TODO section, but that example is a proposal for what might be added. Constructor field parameters and adjustment-block parameters should not be treated as interchangeable available facilities in this release.

Roles, broader metaprogramming, and extension-defined attributes likewise remain on the documented work list. Their absence matters for comparing the native implementation with an existing object framework. Availability of native classes does not establish parity with every facility a framework may supply.

The warning category experimental::class keeps that provisional status visible. Suppressing it removes the warning and leaves the experimental contract intact. Libraries exposing classes also make the interpreter requirement part of what their callers must provide. Perl 5.38 supplies a concrete model of private instance storage and construction, while the documentation identifies the object-system work that remains outside that model today.