Handling errors#

Every I/O operation can fail. The filesystem can be full, the permissions can be wrong, the file can be missing, the network mount can vanish mid-read. Perl does not raise exceptions for these by default — it returns a false value and sets $!. The programmer’s job is to notice.

The or die idiom#

open my $fh, "<", $path   or die "open $path: $!";

The form has three loadbearing parts:

  • or, not ||. Precedence matters: open(...) || die would group the || against the full argument list of open. Using or binds lowest of all the operators involved and always does what it reads as.

  • The filename in the message. "$!" alone says “Permission denied” with no context. "open $path: $!" says what file, in what step. When logs are all you have, context is everything.

  • $!, not $@. $! is the system error — errno with a human-readable face. $@ is the exception from the most recent eval. Mixing them up at 3am has happened to everyone.

Every open, every close, every print, every read, every binmode in production code has a check. There is no “it obviously cannot fail here”. The one time it does, you will want the diagnostic.

Read $! immediately or not at all#

$! reflects the most recent system call. A naive sequence:

open my $fh, "<", $path;
my $line = <$fh>;                         # might also set $!
warn "something failed: $!"  unless $line;

If the open failed, $! is already overwritten by the time <$fh> runs on the undefined handle. Check the return value of each call right where you call it.

Pattern: check the close as carefully as the open#

open  my $fh, ">", $path   or die "open $path: $!";
print $fh @records         or die "write $path: $!";
close $fh                  or die "close $path: $!";

On a write handle, close is where the kernel actually commits the buffered data. If the disk is full, print may have succeeded into memory and close is where you find out. The close check is not a stylistic flourish; it is the one that catches the half-written log entry.

For pipes, close additionally waits for the child and sets $?. See Pipes for the diagnostic wrapper that decomposes $? into signal and exit-status components.

autodie — errors become exceptions#

For code where every line is a ... or die ... chain, the autodie pragma is worth the import:

use autodie qw(:all);

open my $fh, "<", $path;
while (my $line = <$fh>) {
    # ...
}
close $fh;

With autodie, built-ins like open, close, print, read, chdir, unlink, and a long list of siblings throw a structured exception on failure instead of returning false. The caller catches them with eval { ... } or a block-form try, and the message names the call and the path automatically.

Two practical notes:

  • autodie is lexical. It applies only inside the block where it is use-d. A module you call from an autodie block does not pick up the pragma.

  • autodie does not change the return convention for the success case. open still returns a true value; $fh is still the handle. You can delete your or die suffixes and everything else keeps working.

What the Perl exception looks like#

Whether you die manually or let autodie do it, the exception travels up the stack until something catches it. Catching is eval plus a check of $@:

my $fh = eval {
    open my $h, "<", $path or die "open $path: $!\n";
    $h;
};
if ($@) {
    warn "could not read $path: $@";
    # ... fall back to a default, or return an error, or re-die ...
}

Two habits that save time:

  • Terminate die strings with \n when you want the raw message. Without a trailing newline, Perl appends " at FILE line N" to the die string. Sometimes that is what you want; often it is noise.

  • Prefer structured objects for non-trivial errors. A bless { path => ..., errno => $!, step => "open" }, 'IOError' costs three lines and gives the caller something to introspect instead of a string to regex.

A minimal checklist#

Every open-and-process block should look, structurally, like this:

open  my $fh, MODE, $path   or die "open $path: $!";
binmode $fh, $layer         or die "binmode $path: $!"
                               if $layer_not_already_in_mode;

# ... do I/O; each op checked in-place ...

close $fh                   or die "close $path: $!";

If the code deviates — a missing check, a || where an or belongs, a $@ where $! belongs — that is where the bug is hiding.

Where to go next#

This chapter covered the everyday open. When you need flag-level control over the system call itself — for example, O_EXCL to refuse to clobber, or O_NONBLOCK for a pipe you intend to select on — reach for sysopen and its unbuffered companions sysread and syswrite.