LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2518 min read

Error Handling (eval & die)

Learn how Perl handles errors with die and eval, how to inspect $@, and how this compares to exceptions in other languages.

Introduction

Real programs fail: files go missing, network calls time out, users type nonsense into a form. Perl gives you two simple building blocks for dealing with this gracefully - die to raise an error, and eval to catch it. Together they form Perl's version of exception handling, and they show up constantly in production scripts.

What You Will Learn
  • How die raises a fatal error and stops execution.
  • How eval catches that error instead of letting the program crash.
  • How to inspect the special variable $@ to find out what went wrong.
  • How Perl's approach compares to try/catch in other languages.

Why Error Handling Matters

Without any error handling, a single failed file open or a division by zero will kill your entire script - even if the failure only affects one record out of ten thousand. Proper error handling lets you decide what "failure" means for your program: log it and continue, retry, clean up a partially-written file, or report a friendly message instead of a raw Perl crash dump.

The die Function

die raises a fatal error. If nothing catches it, the program prints the message to STDERR and exits with a non-zero status. die takes a string message; if that message does not end in a newline, Perl helpfully appends " at SCRIPT line N." so you can find where the error came from. If you end your message with a newline yourself, that suffix is suppressed - useful when you are writing a clean, user-facing error message.

use strict;
use warnings;
my $age = -5;
die "Age cannot be negative: $age\n" if $age < 0;
print "Age is valid.\n";
Output

Click Run to see what this code prints.

The Trailing Newline Trick

die "message\n" prints just your message. die "message" (no newline) also prints " at yourscript.pl line 12." which is great for debugging but ugly for user-facing errors.

Catching Errors with eval

Wrapping code in eval { ... } runs it in a protected context. If anything inside the block dies, Perl does not crash the whole program - instead, eval returns undef, execution continues right after the block, and the error message is stored in the special variable $@.

use strict;
use warnings;
sub divide {
my ($a, $b) = @_;
die "Cannot divide by zero\n" if $b == 0;
return $a / $b;
}
my $result = eval { divide(10, 0) };
if ($@) {
print "Caught an error: $@";
} else {
print "Result: $result\n";
}
Output

Click Run to see what this code prints.

Checking $@ After eval

$@ holds the error from the most recent eval. It is set to an empty string if the eval succeeded, and to the die message (or object) if it failed. Because $@ can be overwritten by the very next statement that runs another eval, always check it immediately - and consider copying it into a lexical variable right away.

use strict;
use warnings;
eval {
open(my $fh, '<', 'missing_file.txt') or die "open failed: $!\n";
};
if (my $err = $@) {
print "Error occurred: $err";
}
print "Program continues...\n";
Output

Click Run to see what this code prints.

eval BLOCK vs eval STRING

eval { ... } (a block) is compiled once, at compile time, just like normal code - this is what you should use almost always. eval "string" (a string) is compiled at runtime from a string you build yourself. It works, but it is slower, harder to debug, and risky if the string ever contains untrusted input, since it will happily execute arbitrary Perl code.

use strict;
use warnings;
my $code = '2 + 2';
my $result = eval $code;
print "Result: $result\n";
Output

Click Run to see what this code prints.

Avoid String eval

Only use eval "STRING" when you genuinely need to compile Perl code generated at runtime. Never pass user-supplied input into it - it is equivalent to JavaScript's eval() and carries the same security risk.

Structured Error Objects

die does not require a plain string - you can die with a reference, including a blessed object. This lets larger applications carry structured information (an error code, a category, extra data) instead of just a sentence, which is much easier for calling code to act on programmatically.

use strict;
use warnings;
package MyError;
sub new {
my ($class, %args) = @_;
return bless { message => $args{message}, code => $args{code} }, $class;
}
sub message { return $_[0]->{message} }
sub code { return $_[0]->{code} }
package main;
eval {
die MyError->new(message => 'User not found', code => 404);
};
if (my $err = $@) {
if (ref($err) && $err->isa('MyError')) {
printf "Error %d: %s\n", $err->code, $err->message;
} else {
print "Unexpected error: $err";
}
}
Output

Click Run to see what this code prints.

Perl vs Exceptions in Other Languages

If you know try/catch from Java, JavaScript, or Python, die/eval will feel familiar: die is like throw, and eval is like try. The historical difference is syntax - Perl bolted this onto an existing function-and-block system rather than adding new keywords. Modern Perl (5.34 and later) actually does add real try/catch syntax as an experimental feature, which reads much closer to other languages.

use strict;
use warnings;
use feature 'try';
no warnings 'experimental::try';
try {
die "Something broke\n";
}
catch ($e) {
print "Caught: $e";
}
Output

Click Run to see what this code prints.

Try::Tiny

Before native try/catch existed, most Perl codebases used the CPAN module Try::Tiny to get similar clean syntax on older Perl versions. You will still see it in plenty of production code.

Nested and Re-thrown Errors

It is common to catch a low-level error, add context, and re-throw it so a caller further up the stack knows something failed without needing to know the internal details.

use strict;
use warnings;
sub risky_operation {
eval {
die "low-level failure\n";
};
if ($@) {
die "risky_operation failed: $@";
}
}
eval {
risky_operation();
};
print "Final error: $@" if $@;
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Not checking $@ immediately after eval - it can be overwritten by the very next statement.
  • Forgetting the trailing \n in die messages, which clutters output with "at FILE line N".
  • Reaching for eval "STRING" when eval { BLOCK } would work and is far safer.
  • Assuming eval catches everything - it does not catch process signals like SIGKILL, and some fatal internal errors are unrecoverable.
  • Overwriting $@ by running a second, unrelated eval before you have finished checking the first one.

Best Practices

  • End die messages with \n unless you specifically want the "at FILE line N" debugging suffix.
  • Copy $@ into a lexical variable right after eval: my $err = $@;
  • Prefer eval { BLOCK } over eval "STRING" in almost all cases.
  • Use structured error objects (or at least consistent error codes) for larger applications.
  • Consider Try::Tiny or the core try/catch feature for more readable error-handling code.
  • Always check the return value of operations that can fail, such as open() and system().

Frequently Asked Questions

No. eval only catches fatal errors raised by die (or other runtime failures). Warnings go through warn() and can be intercepted separately with $SIG{__WARN__}.

It is set to an empty string, so a simple if ($@) check reliably tells you whether the eval block failed.

Yes - if there is no surrounding eval, die terminates the whole program (or is caught by an eval further up the call stack, if one exists).

Conceptually yes, but Perl is more flexible and less strict: $@ can hold a plain string or a blessed object, and there is no compile-time requirement to declare what a function might throw.

Key Takeaways

  • die raises a fatal error and stops execution unless it is caught.
  • eval { BLOCK } catches errors from die and lets the program continue.
  • $@ holds the error message (or object) after eval - check it immediately.
  • die can throw a blessed object for structured, machine-checkable errors.
  • Modern Perl offers a native try/catch syntax as an alternative to eval/$@.

Summary

die and eval give Perl a simple but powerful error-handling model: raise a fatal error with die, catch it safely with eval, and inspect what happened through $@. Whether you use the classic $@ idiom, Try::Tiny, or the newer native try/catch syntax, the underlying mechanism is the same. Next, you will look at how to format output cleanly with sprintf and printf.

Next Lesson →

String Formatting (sprintf)