Control flow

try#

Run a block and divert any exception it throws into a catch block, with an optional finally block that always runs on the way out.

try/catch is Perl’s structured exception-handling syntax. The try block executes; if any statement in it throws (via die, an uncaught exception from a callee, or a runtime error), control transfers to the catch block with the exception value bound to the parenthesised lexical. If nothing throws, the catch block is skipped. Unlike eval, a return inside try returns from the enclosing sub, not from the block — try is control flow, not a call frame.

The syntax must be enabled with a feature guard:

use feature 'try';

Synopsis#

try BLOCK catch (VAR) BLOCK
try BLOCK catch (VAR) BLOCK finally BLOCK

What you get back#

try is a statement, not an expression, but it yields the last evaluated value of whichever branch ran — the try block on success, or the catch block on failure. Used as the final statement of a sub or a do block, that value becomes the result:

my $value = do {
    try {
        fetch_thing(@args);
    }
    catch ($e) {
        warn "fetch failed: $e";
        $DEFAULT;
    }
};

Do not write return inside a try that is being used this way — return exits the enclosing sub, bypassing the assignment.

A finally block’s final expression is discarded; it cannot contribute to the value.

Global state it touches#

  • $@ is not the mechanism here. try/catch binds the exception to the lexical named in catch (VAR), not to $@. Code inside catch sees whatever $@ happened to hold before the try; rely on the lexical.

  • $_, $!, and other dynamic globals are untouched by the construct itself — only by whatever code runs inside the blocks.

The catch variable#

catch must be followed immediately by a parenthesised variable declaration. my is implicit — write catch ($e), not catch (my $e). The variable is lexical to the catch block and holds the thrown value exactly as die delivered it: a string, a blessed reference, or any scalar:

try {
    risky();
}
catch ($e) {
    if (ref $e && $e->isa('My::Exception')) {
        $e->rethrow if $e->is_fatal;
        warn $e->message;
    }
    else {
        warn "plain die: $e";
    }
}

There is no multi-catch dispatch on exception class — write the classification yourself inside the single catch block.

finally#

An optional third block runs on every exit path from the try/catch construct: normal fall-through, exception caught by catch, exception rethrown from catch, or a non-local transfer (return, last, next, redo, goto) out of either block.

try {
    open_resource();
    use_resource();
}
catch ($e) {
    warn "failed: $e";
}
finally {
    close_resource();
}

finally is the right place for release code that must run no matter how the protected section exits — file handles, locks, restoring global state.

A finally block has restrictions the other two blocks do not:

  • No return, no goto, no last / next / redo. Attempting any of these is a compile-time or runtime error.

  • The final expression value is ignored — finally cannot contribute to the construct’s yielded value.

finally remains experimental in Perl 5.42 and emits a warning in the experimental::try category when used. try and catch themselves were de-experimentalised in 5.40 and no longer warn.

Contrast with eval { ... } / $@#

The older pattern is still supported, but try/catch fixes three long-standing pitfalls:

Pitfall

eval / $@

try / catch

$@ clobbering between eval exit and the if ($@) check

real hazard, needs local $@ dance

exception bound to a fresh lexical

return inside the protected block

returns from the eval, not the sub

returns from the enclosing sub

Distinguishing “no error” from “error was false”

$@ can be falsey-but-set

catch only runs on a real throw

Use try for new code. Reach for eval EXPR only when you actually need string-eval’s runtime compilation.

Examples#

Trap an exception and log it:

use feature 'try';

try {
    my $x = call_a_function();
    $x < 100 or die "too big: $x";
    send_output($x);
}
catch ($e) {
    warn "unable to output a value: $e";
}
print "finished\n";

Use try as an expression inside do to pick a fallback:

use feature 'try';

my $config = do {
    try { load_config($path) }
    catch ($e) { warn "using defaults: $e"; default_config() }
};

Release a resource unconditionally with finally:

use feature 'try';
no warnings 'experimental::try';   # finally is still experimental

my $lock = acquire_lock();
try {
    do_critical_work();
}
catch ($e) {
    warn "critical work failed: $e";
}
finally {
    release_lock($lock);
}

Classify exceptions inside a single catch block:

use feature 'try';

try {
    fetch_remote();
}
catch ($e) {
    if (ref $e && $e->isa('Net::Timeout')) {
        retry_later();
    }
    elsif (ref $e && $e->isa('Net::Auth')) {
        die $e;                     # rethrow auth failures
    }
    else {
        warn "unexpected: $e";
    }
}

return inside try returns from the enclosing sub — not just the block:

use feature 'try';

sub first_valid {
    for my $path (@_) {
        try {
            return load($path);     # exits first_valid on success
        }
        catch ($e) {
            warn "skip $path: $e";
        }
    }
    return;                         # all paths failed
}

Edge cases#

  • Feature guard required. Without use feature 'try' (or a use v5.34 or later bundle that enables it), try is a plain identifier and the construct is a syntax error.

  • catch is mandatory. try { ... } on its own is not legal — every try needs a catch. Use defer or finally if you want cleanup without exception handling, paired with a trivial catch ($e) { die $e } rethrow if you also want to let the exception propagate.

  • Rethrowing. Inside catch, die $e rethrows. There is no implicit rethrow if you simply fall off the end of catch; doing so swallows the exception. Decide deliberately.

  • $@ is not cleared by entering try and is not set by a caught exception. Do not read $@ inside catch expecting the thrown value — read the lexical.

  • caller() does not see try. Like while or foreach, try is not a call frame. caller skips it, which is correct: since return propagates through try, try has no independent return semantics worth exposing to introspection.

  • Loop controls pass through. last, next, and redo inside try or catch act on the enclosing loop, not on the try construct. A finally block will still run before the transfer completes.

  • finally restrictions are enforced. return, goto, and loop controls inside finally are errors. If you need conditional cleanup that may abort, do it in catch or outside the construct.

  • No try-expression sugar beyond last-statement yield. try is not a general expression; it can only produce a value when it is the final statement of a block that yields (do, a sub). my $x = try { ... } catch ($e) { ... }; is a syntax error.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

  • try and catch are non-experimental (as of 5.40) and emit no warnings.

  • finally is still experimental in 5.42 and emits experimental::try warnings when used; silence with no warnings 'experimental::try'.

See also#

  • catch — the mandatory second block; receives the exception in a parenthesised lexical

  • finally — optional third block for unconditional cleanup; still experimental in 5.42

  • die — the throw side of the construct; any scalar (string, blessed ref, object) can be the thrown value

  • eval — the older block-eval / $@ pattern, still needed for string eval; prefer try for pure exception handling

  • defer — unconditional cleanup without a surrounding try/catch; the semantic basis on which finally is defined

  • return — inside try returns from the enclosing sub, not from the block; inside finally it is an error