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