Attributes#

A sub attribute is a colon-prefixed annotation between the sub’s name (or signature) and its body. Attributes mark a sub as having some special property — being a method, being an lvalue, having a prototype, being constant, being subject to some third-party mechanism — that the runtime, the parser, or a user-written attribute handler can act on.

sub greet :method ($name)            { ... }
sub editable :lvalue                 { ... }
sub mypush :prototype(\@@)           { ... }
sub PI :const                        { 3.14159 }
sub handler :Memoize                 { ... }   # third-party attribute

The four attribute steps#

To follow how an attribute affects a sub, distinguish four phases:

  1. Parse. The compiler reads :foo(args) and breaks it into a name (foo) and an optional parameter string (args).

  2. Register. The compiler invokes either a built-in handler (for :lvalue, :method, :prototype(...)) or MODIFY_CODE_ATTRIBUTES from the relevant package (for user-defined attributes).

  3. Store. Built-in attributes set a flag on the CV (the compiled sub object). User attributes are stored wherever the handler chooses.

  4. Query. Anyone can later ask attributes::get(\&sub) for the attribute list. Built-in attributes are also queryable through more direct APIs (prototype, is_lvalue, …).

The four phases are why attributes are heterogeneous in behaviour: built-in attributes are runtime concepts; user attributes are whatever code in MODIFY_CODE_ATTRIBUTES decides to do with them.

Built-in attributes#

:lvalue#

Marks the sub as usable on the left-hand side of an assignment. The body’s last expression must be an lvalue. See lvalue and context.

sub editable :lvalue { $self->{x} }

$obj->editable = 42;             # writes through to $self->{x}

:method#

Hint to the parser and to introspection that this sub is meant to be called as a method. The flag is mostly informational; it also suppresses prototype effects (since methods don’t honour prototypes anyway):

package Counter;

sub increment :method {
    my $self = shift;
    $self->{n}++;
}

:method does not automatically use parent or set @ISA; it does not change dispatch. It is a documentation / intent marker that the runtime also uses to skip prototype processing.

:prototype(...)#

The attribute form of a prototype, required when signatures are enabled in the same scope:

use feature 'signatures';
sub mygrep :prototype(&@) ($code, @list) {
    grep { $code->($_) } @list;
}

The body’s parameter list is the signature; the attribute carries the prototype. Without the attribute form, the parser cannot tell which parenthesised list is which.

:const#

Promises the runtime that the sub’s body produces a constant result, enabling more aggressive inlining than the bare () prototype gives:

sub PI :const { 3.14159265358979 }

:const was introduced for use by use constant and similar tools. Most code should reach for use constant rather than applying :const directly.

Third-party attributes#

Any package can register attribute handlers for code refs by implementing MODIFY_CODE_ATTRIBUTES. The classic example is Attribute::Handlers, which provides a more declarative API on top of the raw mechanism. A user attribute looks identical to a built-in at the call site:

use Some::Memoize;               # provides ':Memoize' attribute

sub expensive :Memoize {
    ...
}

The package providing the attribute decides what :Memoize means. Common third-party attributes:

  • :Memoize — automatically cache return values (from Memoize).

  • :Logged — wrap the sub with logging entry/exit (from various AOP-style modules).

  • :Test, :Benchmark — testing-framework markers.

When you see a colon-attribute that isn’t on the built-in list above, find the package supplying it.

How attributes are written#

Several attributes can be chained. The ordering on disk is the ordering they’re processed in:

sub fancy :method :prototype(\@) :Memoize {
    ...
}

Attributes attach to both a forward declaration and the definition; if you write both, the attribute lists must agree:

sub greet :method;               # forward declaration with :method
sub greet :method ($name) {      # body, same attribute
    ...
}

A signature, when present, comes after attributes:

sub greet :method ($name) {      # OK
    ...
}

sub greet ($name) :method {      # error: attributes must precede signature
    ...
}

What attributes do not do#

  • They are not type annotations. :Int is not a built-in; attempts to read it that way will silently do nothing unless some package has registered a handler that means the same thing.

  • They are not metadata for the test framework unless the test framework registered a handler for them. Sprinkling :tested on subs does nothing without code on the other side reading the annotation.

  • They are not optimisation directives. :fast does nothing.

Querying a sub’s attributes#

use attributes;

sub demo :method :prototype($) :Memoize { ... }

my @attrs = attributes::get(\&demo);
# returns: ('method', 'prototype($)', 'Memoize')

This is the introspection equivalent of prototype — it answers «what annotations does this sub carry?» rather than «what does this sub look like to the parser?».

See also#