tags:

views:

112

answers:

3

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?

+4  A: 

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" }
Sinan Ünür
Thanks, that's interesting. But since it uses the `DESTROY` method doesn't it mean that it's at the whim of the garbage collector rather than deterministically working when the scope exits? I think `Sub::ScopeFinalizer` or `Hook::Scope` (linked from `Scope::Guard`) are closer to what I want.
Philip Potter
Perl uses reference counting, not a 'real' garbage collector, so it is deterministic.
Leon Timmermans
Looking more carefully, Hook::Scope, Hook::Lexwrap, Sub::ScopeFinalizer and Scope::OnExit either use a `sub DESTROY` or an XS `SAVEDESTRUCTOR_X`. If there's a way to do this without destructors, I haven't found one.
Philip Potter
SAVEDESTRUCTOR_X is a very different mechanism than sub DESTROY. It is directly tied to the scope, and thus entirely deterministical. They execute in LIFO order.
Leon Timmermans
@Leon and @Philip which is why I really liked `Scope::OnExit` and I think the OP might still be best served by using that module.
Sinan Ünür
@Leon, thanks, that's great to know. For reference: `Scope::OnExit` and `Hook::Scope` use `SAVEDESTRUCTOR_X`. `Sub::ScopeFinalizer`, `Scope::Guard` and `Hook::LexWrap` use `sub DESTROY`.
Philip Potter
See also the perl.com article "Better Code Through Destruction", at http://www.perl.com/lpt/a/993
bart
+2  A: 

The nice thing about lexical filehandles is that they'll get closed (and freed) when they go out of scope. So you can just do something like this:

{
    # bare block creates new scope
    open my $fh, "<", "filename" or die "Couldn't open filename: $!";
    eval { process($fh) };

    # handle exceptions here

    close $fh or die "Couldn't close filename: $!";
}

# $fh is now out of scope and goes away automagically.
friedo
+3  A: 

My module Scope::OnExit is intended for exactly that.

Leon Timmermans
+167 Nice ... very, very nice.
Sinan Ünür
Yes, it's nice, but it depends on the destructor which perltoot states is not called deterministically. Even if the GC "really is" deterministic, it is not documented as being deterministic. I'd rather not rely on undocumented behaviour.
Philip Potter
No, it does NOT depend on a destructor in fact
Leon Timmermans