finally#
Run cleanup code on the way out of a try/catch, whether the body
succeeded, threw, or jumped away.
finally introduces a third block that can follow a try/catch
pair. Its body runs exactly once, after the try body completes —
however it completes. A normal fall-through, an exception caught by
the catch, an exception that escapes because there is no matching
catch, a return from the enclosing sub, a goto, a last/next/
redo leaving the enclosing loop: every one of these routes through
the finally block before control continues. The pattern is
equivalent to scheduling a defer block at the top of the try,
with a syntax that keeps the cleanup visually attached to the
try/catch it belongs to.
Synopsis#
use feature 'try';
no warnings 'experimental::try';
try { BODY }
catch ($e) { HANDLE }
finally { CLEANUP }
What you get back#
finally has no return value. The final expression of the block is
evaluated for its side effects and then discarded — it does not
contribute to the value of the enclosing try/catch expression,
even if the whole construct is the last statement of a sub or do
block being used to produce a value.
my $v = do {
try { compute() }
catch ($e) { $DEFAULT }
finally { log("done"); 999 } # 999 is discarded
};
# $v is either compute()'s value or $DEFAULT
Use finally for cleanup that has to happen, not for computing a
result.
Feature status#
finally is part of the try feature, but unlike try and catch
(which became non-experimental in Perl 5.40) it remains experimental
in Perl 5.42. Using it without silencing the warning produces:
finally is experimental at ... line N.
Enable the feature and silence the warning together:
use feature 'try';
no warnings 'experimental::try';
Because the warning category is experimental::try — the same one
that try/catch used to emit — silencing it enables finally
without re-enabling warnings on the now-stable parts of the feature
you do want diagnostics for.
When the block runs#
The contract is “on the way out, no matter how.” Concretely, finally
runs after each of the following:
Normal completion of
try. Thetrybody fell off the end or returned its last expression value;finallyruns next, then the statement after thetryconstruct.Exception caught by
catch. Thetrybody threw, thecatchmatched and handled it;finallyruns after thecatchbody.Exception not caught. There is no
catch, or thecatchitself re-threw withdie;finallystill runs, then the exception continues propagating to the next dynamiceval/tryoutward.returninsidetryorcatch. The enclosing sub is about to return;finallyruns, then the return takes effect.Loop control (
last,next,redo) insidetryorcatch.finallyruns, then the loop control takes effect in the enclosing loop.goto &suborgoto LABELleaving thetry.finallyruns before the jump completes.
The ordering matters: if the catch itself raises a new exception,
finally still runs, and the new exception is the one that
propagates (the original is replaced, just as with any die inside a
handler).
Restrictions on the block body#
A finally block is a cleanup point, not a control-flow redirect.
Perl rejects any control-flow statement that would try to override
how the construct is already unwinding:
returninsidefinallyis a syntax / compile-time error.gotoinsidefinallyis a syntax / compile-time error.Loop controls (
last,next,redo) targeting a loop outside thefinallyblock are rejected.
Inside a plain loop that is contained in the finally body, the
loop controls apply to that inner loop as normal — the restriction is
on leaving the finally block, not on looping inside it.
die is permitted. A finally block that throws replaces any
in-flight exception from the try/catch with the new one. This is
usually a bug (you lose the original failure), so guard cleanup code
that might itself fail:
finally {
eval { $fh->close }; # swallow close errors during cleanup
}
Relationship to defer#
finally BLOCK is “defer BLOCK placed at the top of the try,
attached instead to the try/catch pair.” Both run on every exit path
from their containing scope; both forbid the same control-flow
statements in their body; both discard their return value.
The difference is scope attachment:
deferattaches to the innermost enclosing block and fires on that block’s exit. It is the general-purpose cleanup primitive and works in any block.finallyattaches to thetry/catchconstruct and fires when that construct is done. It is syntactic sugar for the common case where the cleanup is specifically about what thetrywas doing.
# These two are behaviourally equivalent:
try {
my $fh = acquire();
defer { release($fh) }
work($fh);
}
catch ($e) { warn $e }
try {
my $fh = acquire();
work($fh);
}
catch ($e) { warn $e }
finally { release_last() } # if $fh were available here
Note one practical consequence: variables declared inside the try
body go out of scope before finally runs. If the cleanup needs a
resource allocated inside the try, either declare it in an outer
scope, or use defer inside the try where the variable is live.
Examples#
Always close a handle, even on error:
use feature 'try';
no warnings 'experimental::try';
open my $fh, '<', $path or die "open $path: $!";
try {
process($fh);
}
catch ($e) {
warn "processing failed: $e";
}
finally {
close $fh;
}
Release a lock regardless of outcome:
my $lock = $mutex->lock;
try {
critical_section();
}
finally {
$lock->release;
}
Observability: log every exit path:
try {
run($job);
}
catch ($e) {
$job->mark_failed($e);
die $e; # re-throw; finally still runs
}
finally {
metrics->tick('job.exit', $job->id);
}
Value-producing try/catch, with finally for cleanup only:
my $result = do {
try { fetch($url) }
catch ($e) { $CACHED }
finally { close_connection() }
};
# $result is fetch() or $CACHED; finally's value is never used
Edge cases#
catchcan be omitted.try { ... } finally { ... }is legal and runsfinallyon both normal and exceptional exit; the exception then propagates as if notrywere there.Nested
try/finally:finallyblocks fire innermost-first as scopes unwind, matchingdefersemantics.Exception inside
finally: replaces any in-flight exception. The original is lost unless you captured it in thecatchfirst.$@duringfinally: not reliable for “what just happened.” If thefinallyneeds to know whether it is running after success or failure, set a flag in thecatchblock and inspect it:my $failed; try { risky() } catch ($e) { $failed = $e; die $e } finally { if ($failed) { rollback() } else { commit() } }
Global destruction:
finallyblocks registered by atrythat is still active when the interpreter starts tearing down run, but the same caveats asdieinsideDESTROYapply — keep the body defensive.Parse requires the feature: without
use feature 'try', the wordfinallyis not a keyword and parses as a bareword function call, which usually produces a confusing unrelated error. Always enable the feature before using the syntax.
Differences from upstream#
Fully compatible with upstream Perl 5.42.
See also#
try— introduces the block whose exitfinallyhooks; see Try Catch Exception Handling in perlsyncatch— the optional middle block that handles exceptions thrown bytry;finallyruns after it either waydefer— the general-purpose “run on scope exit” block;finallyisdeferattached to atry/catchpairdie— the only way to cause thecatchbranch to fire; adieinsidefinallyreplaces any in-flight exceptioneval— the older exception-catching mechanism;try/catch/finallysupersede the commoneval { ... }; if ($@)idiom with cleaner scoping and proper control-flow semantics