Scoping · Classes and OO

field#

Declare a per-instance variable inside a class block.

field introduces a new variable whose storage is private to each object of the enclosing class. Every method and every ADJUST block of that class sees the field as if it were a lexical in scope at that point — but the value each sees is the value belonging to the current instance. Fields are the class feature’s replacement for hash-slot bookkeeping on a blessed reference.

The feature is still marked experimental in Perl 5.42 (see Differences from upstream); enable it with use feature 'class'; and silence the warning category with no warnings 'experimental::class'; if you don’t want the noise.

Synopsis#

field $scalar;
field $scalar = EXPR;
field @array  : ATTRIBUTES;
field %hash   : ATTRIBUTES = EXPR;

What you get back#

field is a declaration, not an expression. It produces no value and cannot appear on the right-hand side of anything. Each field statement allocates one slot per instance in the class’s layout; the slot is the field variable itself, bound to a per-object storage location on entry to any method or ADJUST block.

Scalars, arrays, and hashes are all allowed:

class Thing {
    field $scalar = 42;
    field @array  = qw(this is just an array);
    field %hash   = (species => 'Martian', planet => 'Mars');
}

Field initializers#

If a = EXPR is present, the expression runs once per constructor call, after all fields declared earlier in the class body have been initialized. This matters when a later field’s default depends on an earlier one:

class WithACounter {
    my $next_count = 1;
    field $count = $next_count++;
}

Inside an initializer, $self does not exist — the object is still being built. Use the __CLASS__ token when the initializer needs the class name (for example, to call a class method that subclasses can override):

class WithCustomField {
    use constant DEFAULT_X => 10;
    field $x = __CLASS__->DEFAULT_X;
}

class DifferentCustomField :isa(WithCustomField) {
    sub DEFAULT_X { rand > 0.5 ? 20 : 30 }
}

An instance of DifferentCustomField will see __CLASS__ resolve to its own name, so the initializer picks up the overridden DEFAULT_X.

Field attributes#

Attributes after a colon control how the field participates in construction and whether accessor methods are generated for it.

:param#

Take the field’s value from a named argument to the constructor:

field $x :param;
field $y :param(the_y_value);

By default the parameter name is the field’s name with the sigil stripped ($xx). An explicit name in parentheses overrides that. Without a defaulting expression the parameter is required — omitting it from ->new(...) throws. With a defaulting expression the parameter is optional, and three default operators are available:

  • = — default applies only when the caller omitted the parameter.

  • //= — default also applies when the caller passed undef.

  • ||= — default also applies when the caller passed a false value.

class Point {
    field $x :param = 0;         # default if omitted
    field $y :param //= 0;       # default if omitted or undef
    field $z :param ||= 0;       # default if omitted, undef, or 0
}

:reader#

Generate a zero-argument accessor method that returns the field. Without an explicit name the method is named after the field (sigil stripped):

field $s :reader;

# equivalent to:
field $s;
method s () { return $s }

An explicit name is allowed:

field $x :reader(get_x);        # method get_x () { return $x }

Readers can be applied to array and hash fields too; in list context the method yields the contents, and in scalar context the element count — the usual context behaviour of the underlying variable:

field @users :reader;
...
scalar $instance->users;        # count of users

:writer#

Generate a one-argument setter method that assigns its argument to the field and returns the invocant (to support chaining). The default method name is the field name (sigil stripped) prefixed with set_:

field $s :writer;

# equivalent to:
field $s;
method set_s ($new) { $s = $new; return $self }

An explicit name is allowed:

field $x :writer(write_x);      # method write_x ($new) { ... }

:writer currently only works on scalar fields; applying it to an array or hash field is a compile-time fatal error. Build array or hash writers by hand.

Global state it touches#

None directly. Field storage lives on the instance, and initializer expressions run in the constructor’s scope — they can read any variable visible at the point of declaration, but field itself touches no documented special variable.

Examples#

Minimal class with one field, initialized in ADJUST:

use feature 'class';
no warnings 'experimental::class';

class Greeter {
    field $greeting;

    ADJUST {
        $greeting = "Hello";
    }

    method say_to ($name) {
        say "$greeting, $name";
    }
}

Greeter->new->say_to("world");   # Hello, world

Constructor parameters via :param, with a mix of required and defaulted fields:

class Point {
    field $x :param;             # required
    field $y :param = 0;         # optional, defaults to 0

    method as_string { "($x, $y)" }
}

Point->new(x => 3)->as_string;          # (3, 0)
Point->new(x => 3, y => 4)->as_string;  # (3, 4)

Auto-generated accessors cover the common boilerplate:

class Person {
    field $name :param :reader;
    field $age  :param :reader :writer;
}

my $p = Person->new(name => "Ada", age => 36);
$p->name;            # "Ada"
$p->age;             # 36
$p->set_age(37);     # returns $p

Array and hash fields with defaults:

class Bag {
    field @items = ();
    field %counts;

    method add ($item) {
        push @items, $item;
        $counts{$item}++;
        return $self;
    }

    method unique { keys %counts }
}

Initializer that depends on an earlier field:

class Rect {
    field $w :param;
    field $h :param;
    field $area = $w * $h;       # evaluated after $w, $h are set
}

Edge cases#

  • Lexical visibility is method-scoped, not file-scoped. A field is visible from any method or ADJUST block of the same class, regardless of source file order. It is not visible from plain sub definitions inside the class block — only from method.

  • Per-instance, not per-class. Two instances of the same class have independent storage for every field. Assigning in one instance does not affect another. If you want class-wide state, use a file-scoped my variable outside the field list.

  • Sigil determines type. field $x is a scalar slot, field @a an array slot, field %h a hash slot. Unlike hash-based objects, there is no way to retype a field at runtime; the slot’s container type is fixed by the sigil at declaration.

  • $self is unavailable in initializers. The object is not yet constructed when the initializer runs. Use __CLASS__ for the class name, and defer anything requiring the full object to ADJUST.

  • Initializer order is declaration order. Later initializers may refer to earlier fields; the reverse is a reference to an uninitialized variable.

  • :writer on non-scalar fields is fatal at compile time. The attribute is defined only for scalars in 5.42; applying it to @arr or %h aborts compilation of the class.

  • :param without a default is mandatory. Class->new() with the parameter missing throws. Add = EXPR, //= EXPR, or ||= EXPR to make it optional.

  • No direct external access. Without :reader / :writer (or a hand-written method) a field is unreachable from outside the class — that is the encapsulation the class feature provides.

  • Experimental warning. Parsing a field declaration emits an experimental::class warning unless that category is suppressed; see Differences from upstream.

Differences from upstream#

Fully compatible with upstream Perl 5.42. Upstream marks the class feature — and therefore field — as experimental; pperl tracks the same status and emits the same experimental::class warning. The known upstream bugs listed in perlclass (segfaults around in-file inheritance, refaliasing interactions, and leaky encapsulation) apply equally to pperl.

See also#

  • class — declares the class whose body is the only place field is legal

  • method — the only subroutine form that sees field variables by name

  • my — ordinary lexical declaration; use it for class-wide state that should not be per-instance

  • our — package-global declaration; use it for genuinely global state a class wants to expose

  • bless — the pre-5.38 way to build objects; field replaces the hash-slot bookkeeping that bless-based classes needed

  • ref — check an object’s class at runtime; still works on instances produced by a class constructor