Perl Part I · Module 10 · Subroutines and Arguments
Closures and Private State
A lexical variable normally dies with the block that declared it. If a subroutine was created inside that block and still refers to the variable, it does not — the variable stays alive for exactly as long as the subroutine does, reachable from nowhere else in the program. That gives you private state without a package variable, a global, or a class: a counter nobody else can reset, a helper that already knows its settings, a pair of functions sharing one number. Chapter 7.11 established the mechanism while covering code references. This chapter is about what to build with it, including the one case where two closures are deliberately looking at the same variable, and the newer feature that does a narrower version of the same job in one line.
Advanced15 min readPerlPart ISubroutines
Part I · Module 10 · Chapter 10.10 · Closures and Private State
1. The Mechanism, Briefly
Chapter 7.11 §4 measured this; it is repeated here as the baseline the rest of the chapter builds on.
use strict;
use warnings;
sub make_counter {
my ($n) = @_;
return sub { return $n++ };
}
my $a = make_counter(5);
my $b = make_counter(100);
printf "a: %d %d %d\n", $a->(), $a->(), $a->();
printf "b: %d %d\n", $b->(), $b->();
printf "a again: %d\n", $a->();a: 5 6 7
b: 100 101
a again: 8Three facts, each of which the rest of the chapter depends on:
- The captured variable outlives its scope.
$nis amyvariable insidemake_counter, whose block ended before$a->()was ever called. It persists because the returned subroutine still refers to it — Chapter 7.2 §3's reference-counting rule, applied to a variable rather than a container. - Each call to the maker creates a fresh variable.
$aand$bhave separate counters. - It is genuinely private. Nothing outside
$acan read or write that$n. There is no package variable to collide with, nothing tolocalise, and no way for another part of the program to reach it.
A closure is just a subroutine that refers to a lexical from an enclosing scope. There is no special syntax — sub { return $n++ } becomes a closure purely because $n is not its own.
2. Two Closures Over One Variable
This is the part Chapter 7.11 did not cover, and it is what makes closures more than a counter trick.
use strict;
use warnings;
sub make_pair {
my $count = 0;
my $inc = sub { return ++$count };
my $get = sub { return $count };
return ($inc, $get);
}
my ($inc, $get) = make_pair();
$inc->() for 1 .. 3;
printf "after 3 increments, get() = %d\n", $get->();
my ($inc2, $get2) = make_pair();
printf "a SECOND pair starts at = %d\n", $get2->();
printf "and the first is still at = %d\n", $get->();after 3 increments, get() = 3
a SECOND pair starts at = 0
and the first is still at = 3$inc and $get are looking at the same $count. Three increments through one closure are visible through the other. And the second pair has its own $count, starting at zero, with the first pair unaffected.
That is the general shape: everything created inside one call to the maker shares that call's variables; different calls share nothing. It gives you an object with private fields and no package, class or bless:
sub make_tally {
my %count;
return (
add => sub { $count{ $_[0] }++; return },
get => sub { return $count{ $_[0] } // 0 },
total => sub { my $t = 0; $t += $_ for values %count; return $t },
names => sub { return sort keys %count },
);
}
my %tally = make_tally();
$tally{add}->("alu");
$tally{add}->("alu");
$tally{add}->("fifo");
printf "alu=%d total=%d names=%s\n",
$tally{get}->("alu"), $tally{total}->(), join(" ", $tally{names}->());alu=2 total=3 names=alu fifoFour closures, one %count, and no way to reach %count except through those four. A caller cannot delete a key, cannot set a count to a negative number, and cannot iterate it in an unsorted order — because names sorts, which is Chapter 6.5's requirement enforced by the interface rather than by convention.
The returned hash-of-closures is Chapter 7.11 §2's dispatch table with state behind it.
3. The Practical Use: a Configured Helper
A counter is the textbook example; this is the one that earns its place in real scripts.
use strict;
use warnings;
sub make_formatter {
my (%opt) = (width => 6, sign => 0, @_);
return sub {
my ($v) = @_;
return sprintf("%+*.2f", $opt{width}, $v) if $opt{sign};
return sprintf("%*.2f", $opt{width}, $v);
};
}
my $plain = make_formatter();
my $signed = make_formatter(sign => 1, width => 9);
printf "plain : [%s]\n", $plain->(-0.125);
printf "signed : [%s]\n", $signed->(0.5);plain : [ -0.12]
signed : [ +0.50]make_formatter takes its settings once — using Chapter 10.5's named-argument pattern — and returns a subroutine that needs only the value. The settings are captured, so the inner subroutine has a one-argument interface no matter how many options the maker took.
That is the win: it moves the configuration out of the inner loop.
for my $row (@rows) {
print fmt($row->{slack}, width => 9, sign => 1), "\n"; # repeated 40,000 times
}my $f = make_formatter(width => 9, sign => 1);
for my $row (@rows) {
print $f->($row->{slack}), "\n";
}The second version states the formatting decision once, in one place, and the loop body says only what it is doing. Changing the width is a one-line edit rather than a search for every call.
The same shape covers a comparator configured with a sort field, a validator configured with a tolerance, and a logger configured with a prefix and a filehandle — any case where several calls share settings that are decided once.
Note -0.125 came out as -0.12 rather than -0.13: %.2f rounds half to even, which Chapter 4.8 measured. Worth remembering whenever a formatter's output is compared against an expected string.
4. Closures Against state
Perl 5.10 added a narrower tool for the same job.
use strict;
use warnings;
use feature "state";
sub closure_counter {
my $n = 0;
return sub { return ++$n };
}
sub state_counter {
state $n = 0;
return ++$n;
}
my $c1 = closure_counter();
my $c2 = closure_counter();
printf "two closures : %d %d then %d %d\n", $c1->(), $c1->(), $c2->(), $c2->();
printf "state sub : %d %d %d\n", state_counter(), state_counter(), state_counter();two closures : 1 2 then 1 2
state sub : 1 2 3The two closures are independent — both start at 1. The state variable is not — it counts across every call to the subroutine.
| Closure | state | |
|---|---|---|
| how many instances | one per call to the maker | exactly one, ever |
| created by | returning a sub from a block | state $x = 0; inside a sub |
| needs | nothing | use feature "state" (5.10+) |
| reset | make a new one | no way to reset it |
| good for | per-thing state | a one-off cache or a call count |
state is right when there genuinely should be only one. A subroutine that caches an expensive lookup, counts how many times it was called in total, or warns only on its first invocation:
sub check {
my ($v) = @_;
state $warned = 0;
if (!$warned && $v < 0) {
warn "negative values present; see the full report\n";
$warned = 1;
}
...
}A closure is right when there should be one per something — per file being parsed, per corner being checked, per formatter configuration. state cannot express that at all, because there is only ever one variable.
Note that state $n = 0; initialises once, not on every call — which is the whole point, and also a trap worth knowing: state @a = (1,2,3); was a syntax error before Perl 5.28, so older code uses a scalar holding a reference instead.
5. The Trap, and the Lifetime Cost
6. When Not to Use One
Closures are easy to over-apply. Three cases where something simpler is better:
When nothing is captured. return sub { return $_[0] * 2 } closes over nothing, so it is just an anonymous subroutine — Chapter 7.11 §1's territory. Writing a maker for it adds a layer for no benefit.
When the state should be visible. A counter that a caller is meant to read, reset and report on is a plain variable or a hash field, not a closure. Hiding it behind an accessor pair is work that buys privacy nobody asked for.
When there is one of something and a module would be clearer. Four closures returned in a hash is an object without the word object. For anything with more than a handful of operations, Module 12's packages give you the same privacy with a conventional interface, and a reader who knows Perl will find a package faster than a hash of code references.
The test worth applying: a closure earns its place when state must be private and there is one instance per something. If either half is missing, use a variable, a state variable, or a package.
7. Why This Matters in Engineering Work
Configured helpers remove repetition from hot loops. A formatter, a comparator or a validator that takes its settings once and then takes one argument per call makes the loop body say only what it does — and makes the settings a single line someone can find and change.
Per-file and per-corner state is the common shape. A flow script processing twelve corners wants twelve independent tallies, which is exactly one closure per corner and is not expressible with state.
The loop-variable trap is a real shape in code that builds a list of callbacks, and the measured 4 4 4 is worth remembering: the captured value is whatever ended the loop, which is one past the last value the body ever saw.
And the lifetime cost is worth a thought in a long-running script. Measured with a DESTROY observer: a value the closure does not mention is freed as soon as the maker returns, and one it mentions survives as long as the closure does. Computing the summary first and capturing only that is a one-line difference.
8. Common Mistakes
Expecting two closures from the same maker call to be independent. They share that call's variables — which is usually the point.
Expecting closures from different maker calls to share. They do not; each call makes fresh variables.
Closing over a C-style loop counter. All the closures see the value that ended the loop — measured 4 4 4, not 3 3 3. Copy into a my inside the body.
Using state where you need one instance per thing. There is only ever one state variable.
Expecting state $x = 0; to re-initialise. It runs once.
Mentioning a large array inside a closure that does not need it. The array stays alive for the closure's lifetime.
A maker that captures nothing. That is a plain anonymous subroutine; drop the maker.
Four closures in a hash where a package would read better. Module 12's packages are the conventional form.
9. Exercise
Exercise 1 — the baseline. Write make_counter and build two counters with different starting values. Interleave calls and record the exact sequence. Confirm neither affects the other.
Exercise 2 — shared state. Write make_pair returning an incrementer and a getter. Increment three times through one and read through the other. Then make a second pair and confirm both the new count and the old one.
Exercise 3 — the tally. Write make_tally from §2 returning four closures in a hash. Add two counts for one name and one for another, then report one count, the total, and the sorted names. Confirm you cannot reach %count from outside.
Exercise 4 — the configured helper. Write make_formatter and build a plain and a signed formatter with different widths. Format -0.125 and 0.5. Say why -0.125 printed as it did.
Exercise 5 — settings in the loop. Write a 5-row loop calling a formatting subroutine with its settings passed every time, then rewrite it with a closure. Count the characters of the loop body in each.
Exercise 6 — closure against state. Write closure_counter and state_counter from §4. Call two closures twice each and the state subroutine three times. Record both lines and explain the difference in one sentence.
Exercise 7 — the loop trap. Build three closures with for my $i (1..3) and three with a C-style for. Record both result sets. Explain why the second is 4 4 4 rather than 3 3 3, then fix it with a copy inside the body.
Exercise 8 — capture the smallest thing. Write a maker that parses a list into a large array, computes one summary number, and returns a closure using only that number. Then add a mention of the array inside the closure. Describe what changed about the array's lifetime.
10. Summary
- A closure is a subroutine that refers to a lexical from an enclosing scope. No special syntax; the reference is what makes it one.
- The captured variable outlives its block — Chapter 7.2 §3's rule — and is reachable only through the closure.
- Each call to the maker creates fresh variables — measured, two counters at 5 and 100 ran independently.
- Everything created in one maker call shares that call's variables. Measured: an incrementer and a getter saw the same
$countat 3, while a second pair started at 0. - That gives private state with no package and no globals — four closures over one
%count, unreachable from outside. - The practical use is a configured helper: take the settings once, return a one-argument subroutine, and keep the configuration out of the loop.
stateis the narrower tool — exactly one variable for all calls, measured 1 2 3, against two closures both giving 1 2. Use it for a cache or a warn-once; use a closure when there is one per something.- Closing over a C-style loop counter captures the value that ENDED the loop — measured
4 4 4.foreachgives each pass its own binding. - A closure keeps alive exactly what it mentions — measured with a
DESTROYobserver: the unmentioned value was freed when the maker returned, the mentioned one when the closure was dropped. Compute the summary first and capture only that. - Do not use one when nothing is captured, when the state should be visible, or where a package would read better.
Chapter 10.11 covers the newer way to declare parameters — naming them in the definition itself — and the older feature that looks similar and does something different.
Where this fits
Part of the Perl curriculum.
