Error Handling
๐จ Error Handling
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.
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.
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.
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.
๐ฏ What's Next
Next chapter: Closures & Functional Techniques โ anonymous subs, closures over lexicals, and map/grep/sort with custom blocks.