Perl exception-handling: choosing eval Try::Tiny core try/catch
A practical comparison of Perl exception-handling options across Perl versions, with a compact table and a concrete helper pattern you can validate today.
03 Dec 2025, 23:08 UTC

You're adding error handling to fresh Perl production code and discover the landscape is split between legacy eval/die patterns, lightweight CPAN modules, and a core syntax that arrived experimental in 5.34 and stable in 5.40. The choice locks you into stringly errors, unnecessary dependencies, or fragile upgrade paths.
Decision at a glance
If your minimum supported Perl is 5.40, the stable core try/catch with finally is the default choice. On 5.34-5.39, Syntax::Keyword::Try provides real-block semantics without waiting for a version bump. When CPAN dependencies are off-limits or the floor is older, Try::Tiny offers a small, pure-Perl safety net. Reserve raw eval/die for tiny scripts or legacy patches where a full redesign isn't warranted.
Option comparison
| Option | Perl version | CPAN dependency | finally support | $@ safety | Ergonomics |
|---|---|---|---|---|---|
| raw eval/die | any Perl 5 | none | no | risk: clobbered by destructors | verbose, no block scoping |
| Try::Tiny | Perl 5+ | Try::Tiny (pure Perl) | no (defer only) | localizes $@ during block | anonymous sub blocks, trailing ; required |
| Syntax::Keyword::Try | older Perl via XS plugin | Syntax::Keyword::Try (CPAN) | yes | catch($err) scopes error ref | real block syntax, prototype for core |
| core try/catch | 5.34 experimental, 5.40 stable | none | 5.40 only (with finally) | catch($e) scopes exception ref | cleanest syntax, no $@ pitfalls |
Trade-offs
- Version gating: enabling feature 'try' in a file that also imports Try::Tiny makes the bareword try ambiguous; pick one mechanism per file.
- $@ clobbering: always copy $@ immediately after eval; destructors run during unwinding and can overwrite it before you inspect it.
- Module maintenance: check MetaCPAN and CPAN Testers before standardizing on a module; release status changes over time.
- Mixing mechanisms: avoid mixing try from core and Try::Tiny in the same namespace to prevent ambiguity.
Concrete pattern: helper with catch, rethrow, and cleanup
# Perl 5.34+ core try/catch example (no CPAN dependency)
use feature 'try';
use strict;
use warnings;
sub risky { die 'disk error'; }
sub safe_process {
my $result;
try {
$result = risky();
} catch ($e) {
my $err = $e;
if ($err eq 'disk error') {
die Exception->new(message => $err, code => 'DISK');
}
die $err;
}
return $result;
}
package Exception;
sub new { my ($class, %opts) = @_; bless \%opts, $class; }
sub message { $_[0]{message} // ''; }
sub code { $_[0]{code} // undef; }
1;Verification checklist
- Run
perl -V(or print$]) on the target host to confirm the minimum supported Perl version. - Read the
perldeltafor your exact release (e.g.,perldelta perl5.40.0) to confirmtry/catchandfinallystatus. - Compile syntax:
perl -c script.pl. Expected: syntax OK. - Run a unit test asserting type and message. Using
Test2::Tools::Exception:use Test2::Tools::Exception; my $e = dies { safe_process(); }; ok($e->message eq 'disk error', 'exception message matches'); ok($e->code eq 'DISK', 'exception code set'); - Smoke run: execute the script and verify that cleanup code (in a
finallyblock orENDblock) runs on failure.
Limitations
- Core try/catch and finally support changed across Perl 5.34-5.40; confirm against the
perldeltaof the exact Perl you deploy. - Enabling
feature 'try'in a file that also importsTry::Tinycan make the barewordtryambiguous; pick one mechanism per file. - Always copy $@ immediately after eval; destructors run during unwinding and can overwrite it before you inspect it.
- Module maintenance and CPAN release status are time-sensitive; check MetaCPAN and CPAN Testers before standardizing on a module.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.