Classical OO#
Before the class feature landed
in 5.38, Perl’s OO was built from three primitives: a package, a
reference, and the bless
function. Those three primitives are still present — the modern
class keyword sits on top of the same machinery — and every
pre-5.38 codebase, plus most of CPAN, still uses them directly.
This chapter is for reading and maintaining that code. If you are writing a new class, start with the modern class chapter instead.
The three primitives#
A package defines a namespace. Any package can be a class; the difference is purely how it is used.
A reference holds instance data. The convention is a hash reference, but any reference type works.
blesstags the referent with a class name, so that method dispatch through the reference looks up subs in that package.
That is the whole model. Everything else — constructors, accessors, inheritance, roles — is a convention built on top.
A minimal class#
package File;
sub new {
my ($class, %args) = @_;
my $self = {
path => $args{path},
content => $args{content},
};
return bless $self, $class;
}
sub path { $_[0]{path} }
sub content { $_[0]{content} }
sub print_info {
my $self = shift;
print "This file is at $self->{path}\n";
}
1;
Key conventions, each load-bearing:
sub newis the constructor. It is an ordinary method; the language has no constructor keyword. By convention it is namednewand returns the blessed reference.my ($class, %args) = @_unpacks the invocant first, then the named arguments. A class-method callFile->new(path => ...)passes the class name as the first argument.bless $self, $classattaches the class tag. Using$class(the actual invocant) rather than a hard-coded'File'is what makes the constructor inheritance-safe: a subclass inheritingnewwill bless into the subclass’s name.returnis usually omitted afterblessbecauseblessreturns its first argument.bless $self, $classas the last expression in the sub is idiomatic. We write it explicitly here for readability.
Accessor methods by hand#
The model gives you no accessors; you write them. The terse form
exploits the fact that $_[0] is the invocant:
sub path { $_[0]{path} }
sub content { $_[0]{content} }
The named-parameter form is clearer and what you should write when the accessor does anything non-trivial:
sub path {
my $self = shift;
return $self->{path};
}
A read/write accessor:
sub name {
my $self = shift;
$self->{name} = shift if @_;
return $self->{name};
}
This is the boilerplate the modern class feature
replaces with :reader and :writer.
Why hash references#
Hash references dominate because they let you add and rename
fields without changing the layout, and because named keys survive
a Data::Dumper or JSON::encode round-trip. Array references
are faster and more compact but fragile: renumbering a slot
breaks every access site.
Hash-backed instances have one serious drawback: no encapsulation.
Any caller can reach into the object’s internals with
$obj->{path}. The classical model has no tool to stop them —
self-discipline and code review are the only defences. This is
the single biggest reason to prefer the modern class
feature for new code.
Inheritance with @ISA#
Inheritance is a package-level list named @ISA. Method dispatch
walks it when the current package does not define the method.
package File::MP3;
our @ISA = ('File');
sub print_info {
my $self = shift;
$self->SUPER::print_info;
print "Title: $self->{title}\n";
}
1;
SUPER:: means “look for this method starting one step up the
ISA chain from the package the current method was compiled in”
— not from the object’s class. That subtlety matters in diamond
inheritance; the inheritance chapter covers it.
Declaring the parent — four ways#
There are four spellings of the same idea. Prefer the third for new code you are still maintaining in the classical style.
# 1. Raw @ISA assignment.
package Child;
our @ISA = ('Parent');
# 2. use base.
package Child;
use base 'Parent'; # deprecated in modern Perl
# 3. use parent.
package Child;
use parent 'Parent'; # current recommendation for classical
# 4. @ISA with a require.
package Child;
require Parent;
our @ISA = ('Parent');
use parent handles the load of the parent module and the @ISA
push in one line. use base is an older variant that also
imports Exporter-style fields; avoid it.
The $self invocant#
Every method is a subroutine whose first argument is either the
class name (for class methods like new) or the object (for
instance methods). The convention is to shift it into a variable
named $self:
sub method {
my $self = shift;
...
}
Or unpack it together with other arguments:
sub method {
my ($self, $arg1, $arg2) = @_;
...
}
There is no runtime enforcement. If you forget to shift $self,
your first user-supplied argument silently becomes $self and
chaos ensues. Static analysers catch this; the language does not.
Destructors#
A DESTROY method, if defined, runs when the object’s refcount
falls to zero. Use it to release external resources:
sub DESTROY {
my $self = shift;
close $self->{fh} if defined $self->{fh};
}
Pitfalls that bite every project at least once:
DESTROYruns at unpredictable times. Do not rely on it for correctness-critical cleanup — explicit$obj->closebeats implicit destruction.DESTROYcan see a partially constructed object ifnewcroaked mid-way. Defensivedefinedchecks are cheap insurance.During global destruction (
ENDtime), dependencies may already be gone. ADESTROYthat walks references can fail with “Attempt to free unreferenced scalar” or similar. Wrap risky cleanup ineval { ... }.
AUTOLOAD#
If a method call finds no matching sub in the class or its
ancestors, Perl looks for an AUTOLOAD sub. The called method’s
name appears in our $AUTOLOAD:
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
$name =~ s/.*:://;
return if $name eq 'DESTROY'; # critical
...
}
The DESTROY guard is mandatory: without it, object destruction
triggers AUTOLOAD with $name = 'DESTROY' and you either
misbehave or crash.
AUTOLOAD is powerful and easy to abuse. The cases it earns its
keep:
Lazy accessor generation — synthesise an accessor on first call, then install it as a real sub via typeglob assignment so the next call is direct.
Forwarding — delegate unknown methods to a contained object (but see Roles and delegation for a cleaner approach).
Cases where it hurts more than it helps: pretending to be a
full-blown MOP, catching typos as silent no-ops, implementing
“classes” whose entire surface is AUTOLOAD.
A worked example#
A classical Counter class with read/write accessor and
inheritance:
package Counter;
sub new {
my ($class, %args) = @_;
my $self = {
value => $args{value} // 0,
step => $args{step} // 1,
};
return bless $self, $class;
}
sub value {
my $self = shift;
$self->{value} = shift if @_;
return $self->{value};
}
sub step { $_[0]{step} }
sub tick {
my $self = shift;
$self->{value} += $self->{step};
}
1;
A subclass that doubles the step:
package Counter::Double;
use parent 'Counter';
sub new {
my ($class, %args) = @_;
$args{step} //= 2;
return $class->SUPER::new(%args);
}
1;
Compared with the modern class version, the classical form is about three times as many lines and every one of them is a place to get it wrong.
When classical OO is the right call#
Despite everything, there are legitimate reasons to stick with
bless-based classes:
You are maintaining existing code. Rewriting a working classical class into
classform is a migration project, not a free lunch — see the migration chapter.You need to support older Perl. If your code must run on Perl before 5.38, the
classfeature is unavailable.You need array-backed or code-backed objects.
classonly creates hash-backed instances. If you are building a performance- critical structure with array-reference instances, classicalblessis still the answer.You are doing something the
classfeature deliberately forbids — reopening the package from outside, multiple inheritance, or deep metaprogramming via@ISAmanipulation.
A historical note on CPAN OO systems#
Because the classical model leaves so much to convention, CPAN grew an ecosystem of modules that layer declarative syntax on top:
Moose — feature-complete OO system with a type miniature, roles, method modifiers, a full introspection API, and a large extension ecosystem. Heavy at load time; the dominant choice for large applications built between 2007 and roughly 2020.
Moo — a subset of Moose’s API without the introspection layer. Lighter, pure-Perl, API-compatible enough to let a Moo class and a Moose class share an inheritance tree.
Class::Accessor — generates simple accessors and
new. Minimal, pure-Perl, no roles.Class::Tiny — the smallest of the accessor-generator modules. All accessors are read/write, no type checks.
Object::Pad — the experimental prototype that became the core
classfeature. Still installable on CPAN for code that needs the feature on older Perls.Role::Tiny — role composition for projects that do not use Moose but still want
does-style sharing.
These systems are historical context in pperl. New code in
pperl should use the core class
feature, which covers the ground that Moose/Moo pioneered without
the load-time and dependency cost. When you inherit a codebase
that uses one of them, learn just enough of its surface to stay
productive and plan the migration with the migration
chapter.