RAII in perl

Suppose you have matching start() and end() functions. You want to ensure that each start() is always matched with its corresponding end(), without having to explicitly pepper your code with calls to that function. Here’s a good way to do it in perl — create a guard object:

package Scoper;
sub new {
  my $class = shift; bless({ func => shift },$class);
}
sub DESTROY {
  my $self = shift; $self->{func}->();
}

Here’s an example of its use:

{
  start();
  my $s = Scoper->new(sub { end(); });
  [... do something...]
}
[at this point, end() has been called, even if a die() occurred]

The idea is simply to use DESTROY to perform whatever the cleanup operation is. Once the $s object goes out of scope, it’ll be deleted by perl’s GC, in the process of which, calling $s->DESTROY(). In other words, it’s using the GC for its own ends.

Unlike an eval { } block to catch die()s, this will even be called if exit() or POSIX::exit() is called. (POSIX::_exit(), however, skips DESTROY.)

This is a pretty old C++ pattern — Resource Acquisition Is Initialization. C++’s auto_ptr template class is the best-known example in that language. Here’s a perl.com article on its use in perl, from last year, mostly regarding the CPAN module Object::Destroyer. To be honest, though, it’s 6 lines of code — not sure if that warrants a CPAN module! ;)

RAII is used in SpamAssassin, in the Mail::SpamAssassin::Util::ScopedTimer class.

This entry was posted in Uncategorized and tagged , , , , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

4 Comments

  1. Posted April 2, 2008 at 17:05 | Permalink

    It’s only 6 lines until you add the option of cancelling the callback. Then it grows to 10 or maybe 12 lines.

    And no, it doesn’t warrant one CPAN module. It needs at least two more CPAN modules:

    Hook::Scope, Scope::Guard, Perl::AtEndOfScope

  2. Niall
    Posted April 2, 2008 at 19:29 | Permalink

    Woo, feels like 2002 again.

  3. Posted April 2, 2008 at 22:25 | Permalink

    You might also do something like this:

    
    guarded {
      do_something();
    };
    
    
    sub guarded (&) {
      my $code = shift;
       before_run();
      eval { $code->()};
      my $err = $@;
      after_run();
      die $err if $err;
    }
    
    
  4. Posted April 3, 2008 at 11:24 | Permalink

    Jesse — that works, but with an additional eval {} block — slow!

    Also, the RAII approach can even clean up before an exit(), or POSIX::exit() — not just a die(). check it out:

    $ perl -we 'package Scoper; 
    sub new { my $class = shift; bless({ func => shift },$class); } 
    sub DESTROY { my $self = shift; $self->{func}->(); } 
    { my $x=Scoper->new(sub { print "argh\n"; });
    print "bar\n"; exit; } 
    print "baz\n";'
    bar
    argh