Error Handling

25.1 die and warn

use v5.36;
open(my $fh, '<', $file) or die "Cannot open $file: $!\n";   # $! = OS error message
warn "Config missing, using defaults\n";                    # prints to STDERR, continues
die "Something failed";       # without "\n", Perl appends " at script.pl line N."
die { code => 404, message => 'Not found' };                # die with any value (e.g., a hashref)

use Carp qw(croak carp confess cluck);
sub divide($x, $y) {
    croak "Division by zero" if $y == 0;    # reports the error from the caller's perspective
    return $x / $y;
}
# confess: die with a full stack trace; cluck: warn with a stack trace

25.2 eval Blocks

my $result = eval {
    risky_operation();
    1;                                       # return true on success
} or do {
    my $error = $@ || 'Unknown error';
    warn "Operation failed: $error";
};

# Pitfalls: $@ can be clobbered by DESTROY/nested evals. The idiom above (eval { ...; 1 } or do { ... }) is the safe pattern.
local $@;                                    # protect the caller's $@

25.3 try/catch/finally (feature 'try')

For older Perls: Try::Tiny (try { } catch { warn $_ } finally { }; note the trailing semicolon, and return returns from the block, not the sub), Syntax::Keyword::Try (the CPAN module the core feature was based on), or Feature::Compat::Try.

use v5.36;
use feature 'try';                           # experimental in 5.34–5.38, stable in 5.40
no warnings 'experimental::try';             # needed before 5.40

sub parse_config($path) {
    try {
        open my $fh, '<', $path or die "Cannot open $path: $!\n";
        my $json = do { local $/; <$fh> };
        return decode_json($json);           # return works inside try (returns from the sub)
    }
    catch ($e) {
        warn "Config error: $e";
        return {};
    }
    finally {                                # 5.36+
        say 'Config parse attempted';
    }
}

25.4 Exception Objects

CPAN: Exception::Class (declare hierarchies), Throwable (a Moo/Moose role), Mojo::Exception.

package My::Exception {
    use v5.36;
    sub new($class, %args) { bless { message => $args{message}, code => $args{code} // 500 }, $class }
    sub throw($class, %args) { die $class->new(%args) }
    sub message($self) { $self->{message} }
    sub code($self)    { $self->{code} }
}
package My::Exception::NotFound { our @ISA = ('My::Exception') }

use Scalar::Util qw(blessed);
try {
    My::Exception::NotFound->throw(message => 'User 42 not found', code => 404);
}
catch ($e) {
    if (blessed $e && $e isa My::Exception::NotFound) {
        say 'Not found: ', $e->message;
    } elsif (blessed $e && $e->isa('My::Exception')) {
        say 'App error: ', $e->code;
    } else {
        die $e;                              # rethrow unknown errors
    }
}

25.5 Warnings Control

use warnings FATAL => 'all';                 # turn warnings into errors (strict codebases)
no warnings 'uninitialized';                 # disable a category lexically
local $SIG{__WARN__} = sub { log_warning(@_) };   # intercept warnings
local $SIG{__DIE__}  = sub { ... };          # hook dies (careful: also fires inside eval)