Error Handling

Perl Intermediate/Advanced
Course 2 ยท Chapter 6 ยท Error Handling

๐Ÿšจ Error Handling

Course 1's open(...) or die "..."; idiom raised errors โ€” this chapter covers actually catching and handling them, from the classic eval/$@ approach through Perl's modern, genuinely cleaner try/catch syntax.

๐Ÿ’€ die โ€” Raising an Error

die "Something went wrong";
# Something went wrong at script.pl line 12.   โ€” Perl appends " at FILE line N." automatically

die "Something went wrong\n";
# Something went wrong                          โ€” a trailing \n suppresses the automatic location text

If a die message doesn't already end in a newline, Perl appends the file and line number automatically โ€” genuinely useful for debugging, but the reason some error messages in real scripts show a location and others don't purely depends on whether the message ends in \n.

๐ŸŽฃ eval { } โ€” Catching an Error

eval {
    die "Something failed\n";
};
if ($@) {
    say "Caught: $@";   # Caught: Something failed
}

An eval { } block catches any die raised inside it โ€” execution jumps straight to just after the block instead of terminating the program, and the special variable $@ holds whatever was passed to die (empty string if nothing went wrong). This is the classic, pre-5.34 way Perl has always done exception handling.

โš  $@ Can Be Clobbered Before You Check It
eval { die "oops\n"; };
# if something else runs an eval, or a DESTROY method fires during garbage collection here...
if ($@) { ... }   # $@ might have been silently overwritten by then!

$@ is a global, and anything that runs between your eval and your check of $@ โ€” including an unrelated eval elsewhere, or a destructor firing during garbage collection โ€” can silently overwrite it. Best practice: capture it into your own variable immediately: my $err = $@; if ($err) { ... }.

โœจ Modern try/catch (Perl 5.34+)

use feature 'try';
no warnings 'experimental::try';

try {
    die "Something failed\n";
}
catch ($e) {
    say "Caught: $e";   # Caught: Something failed
}

This is the same error caught the same way โ€” but $e is a genuinely local, unambiguous variable scoped to the catch block, sidestepping every $@-clobbering concern from the warn-box above entirely. On Perl 5.34 or later, this is the recommended approach for new code.

๐Ÿ—๏ธ die with a Reference โ€” Structured Exceptions

eval {
    die { code => 404, message => "Not found" };   # dying with a HASHREF, not a plain string
};
if (my $err = $@) {
    say $err->{code};   # 404 โ€” $@ IS that reference, not a stringified message
}

die accepts a reference just as readily as a string โ€” $@ (or $e in the modern catch form) becomes that exact reference, letting catching code inspect structured data (an error code, a type, extra context) instead of pattern-matching a string message. This is a natural extension of Chapter 3/4's OOP: a real codebase often defines proper exception classes and dies with a blessed object instead of a plain hashref, giving catch code the ability to check ref($e) or call methods on the caught error directly.

๐Ÿ”ง A Worked Example โ€” Robust File Processing

use feature 'try';
no warnings 'experimental::try';

sub process_file {
    my ($filename) = @_;

    try {
        open(my $fh, '<', $filename) or die "Can't open $filename: $!\n";
        while (my $line = <$fh>) {
            # ... process each line ...
        }
        close($fh);
        say "Processed $filename successfully";
    }
    catch ($e) {
        say "Failed to process $filename: $e";
    }
}

This combines Course 1, Chapter 8's file-handling idioms directly with modern try/catch โ€” the open(...) or die pattern still triggers an error exactly as before, but now it's caught and handled gracefully by the surrounding try/catch instead of terminating the whole program.

Error Handling: Perl vs. Python/JavaScript

Perl's die/eval/$@ conceptually parallels Python's raise/except and JavaScript's throw/try/catch โ€” all three "raise an exception, catch it somewhere up the call stack." Historically, Perl's eval-block approach was often described as a "poor man's" version, since it reused a block form originally meant for something else and relied on a global variable ($@) for the error itself. The modern try/catch syntax genuinely converges Perl with the dedicated exception-handling syntax Python and JavaScript have always had โ€” cleaner, and no more global-variable clobbering concerns.

die

Raises an error; a trailing \n suppresses the automatic file/line info.

eval { } / $@

The classic catch mechanism โ€” capture $@ immediately to avoid clobbering.

try / catch ($e)

Modern (5.34+), cleaner syntax โ€” $e is safely scoped, no globals involved.

die with a reference

Structured exceptions โ€” $@/$e becomes the reference itself.

๐Ÿ’ป Coding Challenges

Challenge 1: Catch a Division Error

Write a subroutine safe_divide($a, $b) that dies with "Cannot divide by zero\n" if $b is 0. Call it inside a try/catch block with $b as 0, printing the caught error.

โ†’ Solution

Challenge 2: The $@ Capture Pattern

Write an eval { } block that dies with a message, immediately capture $@ into my $err, and print it. Explain in one sentence why capturing it immediately is safer than checking $@ directly later.

โ†’ Solution

Challenge 3: A Structured Exception

Write a subroutine that dies with a hashref { code => 400, message => "Bad input" } when given a negative number. Catch it and print both the code and message separately.

โ†’ Solution

๐ŸŽฏ What's Next

Next chapter: Closures & Functional Techniques โ€” anonymous subs, closures over lexicals, and map/grep/sort with custom blocks.