Control flow

defer#

Schedule a block to run when the enclosing scope exits, for any reason.

defer is a flow-control keyword, not a function. Writing defer { ... } inside a scope records the block for later execution; when control leaves the enclosing block — by falling off the end, by return, by die, by goto, or by a loop-control statement (next, last, redo) — the stored block runs on the way out. It is the standard idiom for pairing acquisition with release (open/close, lock/unlock, push/pop) without relying on object destructors or wrapping every path in eval.

The feature is experimental and must be enabled. Either opt in explicitly or pick up the bundle:

use feature 'defer';    # explicit
use v5.36;              # bundle — enables defer among others

Under use warnings 'experimental::defer' (or plain use warnings) the first use emits an experimental warning. Silence it with no warnings 'experimental::defer' once you have decided to rely on the feature.

Synopsis#

defer BLOCK

defer takes a brace-delimited block and nothing else. There is no expression form, no filehandle form, and no return value — the statement exists for its side effect on scope exit.

What you get back#

defer is a statement, not an expression. It contributes no value to its surrounding context. The block’s own return value is discarded when it runs at scope exit.

When the deferred block runs#

The deferred block is invoked once, when control leaves the directly enclosing block, regardless of how control leaves:

  • normal fallthrough off the end of the block,

  • explicit return from the containing subroutine,

  • an exception thrown by die or propagated from a called sub,

  • goto out of the block,

  • loop control via next, last, or redo.

If the flow of execution never reaches the defer statement itself, the block is not enqueued and does not run. This is the direct opposite of an END phaser, which the compiler enqueues unconditionally:

use feature 'defer';

{
    defer { say "This will run"; }
    return;
    defer { say "This will not"; }   # never reached, never scheduled
}

LIFO ordering#

Multiple defer blocks in the same scope run in last-in, first-out order — the most recently scheduled block runs first. This matches the natural nesting of resource acquisition: the last thing acquired is the first thing released.

use feature 'defer';

{
    defer { say "1"; }
    defer { say "2"; }
    defer { say "3"; }
}
# prints 3, then 2, then 1

Global state it touches#

None directly. The deferred block sees interpreter globals ($_, $!, $@, selected filehandle, etc.) with whatever values they hold at the moment the block runs, not at the moment it was scheduled. A defer that wants to preserve $! or $@ across cleanup must save and restore them itself — otherwise a system call inside the block will clobber them and the caller of the enclosing sub will see the overwritten values.

defer {
    local ($!, $@);
    close $fh;           # clobbers $! on failure — local'd copy absorbs it
}

Examples#

Pair an open with its close, guaranteed to run even on die:

use feature 'defer';

sub read_config {
    open my $fh, "<", "config.ini" or die "open: $!";
    defer { close $fh; }

    while (<$fh>) { ... }
    # close $fh runs here on fallthrough, on return, and on die.
}

Unlock a shared resource on every exit path:

use feature 'defer';

sub critical_section {
    $lock->acquire;
    defer { $lock->release; }

    do_work();            # release runs on fallthrough
    return if $shortcut;  # release runs before the return completes
    die "boom" if $bad;   # release runs, then the exception propagates
}

LIFO cleanup of nested resources — the outer resource is released last, which is usually what you want:

use feature 'defer';

sub copy_file {
    open my $in,  "<", $src or die "open $src: $!";
    defer { close $in; }

    open my $out, ">", $dst or die "open $dst: $!";
    defer { close $out; }

    print {$out} $_ while <$in>;
    # close $out runs first, then close $in.
}

Temporarily adjust interpreter state and restore it on exit without using local:

use feature 'defer';

sub with_separator {
    my $saved = $,;
    $, = " | ";
    defer { $, = $saved; }

    print @_, "\n";
}

Edge cases#

  • Conditional scheduling. A defer statement only schedules its block when control actually reaches it. An early return or die that happens before the defer line leaves nothing to run. Place the defer immediately after the paired acquisition so they cannot become separated by an intervening failure.

  • Once scheduled, always runs. There is no mechanism to un-schedule a defer block. If the acquisition that the block releases has failed, either do not schedule the defer (keep it after the success check) or make the block idempotent so a redundant release is harmless.

  • No control-flow escape from the block. A defer block may throw an exception, but it may not return from the enclosing sub, goto a label outside itself, or run next, last, or redo for a loop that encloses the defer. Those constructs are fine inside the defer when they act on loops contained entirely within the block’s own body:

    defer {
        foreach (1 .. 5) { last if $_ == 3; }   # permitted
    }
    
    foreach (6 .. 10) {
        defer { last if $_ == 8; }              # forbidden
    }
    
  • Exception during exception unwind. If the defer block is running because an exception is propagating and the block itself throws, the language does not specify the resulting exception — only that the caller will see one. Do not rely on which exception wins. Wrap risky cleanup in eval if you need both signals preserved.

  • Exceptions propagate normally. An exception thrown by a defer block that is running for any other reason (normal exit, return, loop control) propagates to the caller exactly like any other exception. A failing close in cleanup can therefore turn a successful-looking function into a caller-visible die.

  • $@ and $! inside the block. On the exception-driven exit path, $@ holds the in-flight exception while the defer block runs. Reading it is fine; replacing it changes what the caller sees after the unwind completes.

  • Scope granularity. defer binds to the immediately enclosing block, not to the enclosing sub. A defer inside if (...) { defer { ... } } runs when the if block exits, not when the sub returns. Put the defer at subroutine scope if you want subroutine-exit semantics.

  • Experimental warning. Under use warnings the first occurrence warns. Opt in deliberately with no warnings 'experimental::defer' once you have committed to the feature.

  • Not available without the feature guard. Without use feature 'defer' (or a bundle that enables it), defer { ... } is parsed as a call to a subroutine named defer followed by a block — almost certainly not what you meant, and usually a compile-time error.

Differences from upstream#

Fully compatible with upstream Perl 5.42. The feature remains marked experimental in pperl to mirror upstream’s classification; behaviour (scheduling on statement execution, LIFO order, exception propagation, restrictions on control-flow escape) matches Perl 5.42.

See also#

  • eval — the established tool for turning exceptions into return values; defer complements it by guaranteeing cleanup without an explicit eval wrapper on every path

  • die — one of the exit paths that triggers deferred blocks; read alongside defer when designing cleanup

  • return — another exit path; a defer scheduled before a return runs before the caller resumes

  • local — the other idiom for scoped state restoration; pick local for dynamic scoping of a single variable, defer for arbitrary cleanup actions

  • $@ — current exception; visible inside a defer block running on the exception-unwind path

  • $! — system errno; easily clobbered by cleanup syscalls, local-ise inside the deferred block if the caller needs it preserved