Control flow

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. The try body fell off the end or returned its last expression value; finally runs next, then the statement after the try construct.

  • Exception caught by catch. The try body threw, the catch matched and handled it; finally runs after the catch body.

  • Exception not caught. There is no catch, or the catch itself re-threw with die; finally still runs, then the exception continues propagating to the next dynamic eval/try outward.

  • return inside try or catch. The enclosing sub is about to return; finally runs, then the return takes effect.

  • Loop control (last, next, redo) inside try or catch. finally runs, then the loop control takes effect in the enclosing loop.

  • goto &sub or goto LABEL leaving the try. finally runs 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:

  • return inside finally is a syntax / compile-time error.

  • goto inside finally is a syntax / compile-time error.

  • Loop controls (last, next, redo) targeting a loop outside the finally block 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:

  • defer attaches to the innermost enclosing block and fires on that block’s exit. It is the general-purpose cleanup primitive and works in any block.

  • finally attaches to the try/catch construct and fires when that construct is done. It is syntactic sugar for the common case where the cleanup is specifically about what the try was 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#

  • catch can be omitted. try { ... } finally { ... } is legal and runs finally on both normal and exceptional exit; the exception then propagates as if no try were there.

  • Nested try/finally: finally blocks fire innermost-first as scopes unwind, matching defer semantics.

  • Exception inside finally: replaces any in-flight exception. The original is lost unless you captured it in the catch first.

  • $@ during finally: not reliable for “what just happened.” If the finally needs to know whether it is running after success or failure, set a flag in the catch block and inspect it:

    my $failed;
    try   { risky() }
    catch ($e) { $failed = $e; die $e }
    finally {
        if ($failed) { rollback() } else { commit() }
    }
    
  • Global destruction: finally blocks registered by a try that is still active when the interpreter starts tearing down run, but the same caveats as die inside DESTROY apply — keep the body defensive.

  • Parse requires the feature: without use feature 'try', the word finally is 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 exit finally hooks; see Try Catch Exception Handling in perlsyn

  • catch — the optional middle block that handles exceptions thrown by try; finally runs after it either way

  • defer — the general-purpose “run on scope exit” block; finally is defer attached to a try/catch pair

  • die — the only way to cause the catch branch to fire; a die inside finally replaces any in-flight exception

  • eval — the older exception-catching mechanism; try/ catch/finally supersede the common eval { ... }; if ($@) idiom with cleaner scoping and proper control-flow semantics