Search code examples
perlraii

How can I automatically release resources RAII-style in Perl?


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?


Solution

  • 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" }