Say I have a resource (e.g. a filehandle or network socket) which has to be freed:
open my $fh, "<", "filename" or die "Couldn't open filename: $!";
process($fh);
close $fh or die "Couldn't close filename: $!";
Suppose that process
might die. Then the code block exits early, and $fh
doesn't get closed.
I could explicitly check for errors:
open my $fh, "<", "filename" or die "Couldn't open filename: $!";
eval {process($fh)};
my $saved_error = $@;
close $fh or die "Couldn't close filename: $!";
die $saved_error if $saved_error;
but this kind of code is notoriously difficult to get right, and only gets more complicated when you add more resources.
In C++ I would use RAII to create an object which owns the resource, and whose destructor would free it. That way, I don't have to remember to free the resource, and resource cleanup happens correctly as soon as the RAII object goes out of scope - even if an exception is thrown. Unfortunately in Perl a DESTROY
method is unsuitable for this purpose as there are no guarantees for when it will be called.
Is there a Perlish way to ensure resources are automatically freed like this even in the presence of exceptions? Or is explicit error checking the only option?
I think that's what Scope::Guard was designed to help with.
#!/usr/bin/perl
use strict; use warnings;
use Scope::Guard;
my $filename = 'file.test';
open my $fh, '>', $filename
or die "Couldn't open '$filename': $!";
{
my $sg = Scope::Guard->new(
sub {
close $fh or die "Could not close";
warn "file closed properly\n";
}
);
process($fh);
}
sub process { die "cannot process\n" }
However, as @Philip notes in the comments, Scope::Guard
utilizes the DESTROY
method which creates some uncertainty as to when the scope exit code will be run. Modules such as Hook::Scope
and Sub::ScopeFinalizer
look fine as well although I have never used them.
I do like Try::Tiny for its clean interface and sheer simplicity and it will help you handle exceptions the correct way:
#!/usr/bin/perl
use strict; use warnings;
use Try::Tiny;
my $filename = 'file.test';
open my $fh, '>', $filename
or die "Couldn't open '$filename': $!";
try {
process($fh);
}
catch {
warn $_;
}
finally {
close $fh
and warn "file closed properly\n";
};
sub process { die "cannot process\n" }