Golang's 'defer' in Perl

My day job involves programming in Go. One feature I like about Go is "defer". Any function that's deferred gets ran at the end of the functions scope. As a very simple example, the program below prints

hello
world

instead of the other way around, because "world" is deferred until the main() function exits.

package main
import "fmt"

func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}

I find this cleaner: you can write clean-up closer to where you allocate/acquire resources, instead of remembering to do it at the end of your function. In order to do this in perl, I created a defer() function which, given a subref, returns a wrapper subref blessed into a dummy package. Then, I give that dummy package a destructor (DESTROY) which calls itself. The result is that the subroutine given will be run when the variable it is assigned to goes out of scope:

use v5.10.1;
use strict;
use warnings FATAL => "all";

sub defer{
    my ($sub) = @_;
    my $pid = $$;
    return bless sub {
        # I check that I'm running in the process that I called defer() in,
        # because I don't want child processes to call $sub->();
        if ($$ == $pid) { 
            $sub->();
        }
    }, 'my_deferer_dummy_package';
}

sub my_deferer_dummy_package::DESTROY {
    my ($self) = @_;
    $self->();
}

To give a simple usage example, we create a function which opens a file and defers it's close()ing:

sub upper_caser {
    my $file = shift;

    open my $fh, '<:crlf', $file;
    my $defered = defer sub { 
        say "defer function called!";
        close $fh;
    };

    while (defined(my $line = <$fh>)){
        chomp $line;
        say uc $line;
    }
}

there's a file called "test.txt" containing the lines:

just
another
perl
hacker

Now, calling the function,

upper_caser "test.txt";

it yields:

# JUST
# ANOTHER
# PERL
# HACKER
# defer function called!

I also found this very useful in functional programming, when I want to return an iterator subroutine, but also clean-up after it when it is no longer needed:

sub make_upper_caser_iterator {
    my $file = shift;

    open my $fh, '<:crlf', $file;
    my $defered = defer sub { 
        say "defer function in closure called!";
        close $fh;
    };

    return sub{
        # make sure that the closure captures $defered 
        # and keeps it alive as long as this closure is.
        my $defered = $defered;

        if (defined(my $line = <$fh>)){
            chomp $line;
            return $line;
        }
        return;
    };
}

my $iter = make_upper_caser_iterator "test.txt";
while (defined(my $line = $iter->())){
    say uc $line;
}

As expected, the above use of the iterator yields:

JUST
ANOTHER
PERL
HACKER
defer function in closure called!

3 Comments

This is very slick. Scoping and DESTROY for the win!

For what it's worth, Perl 6 has phasers that can handle this sort of thing.

Have a look at Scope::OnExit. There are a number of similar modules on CPAN. I listed the ones I know about in the SEE ALSO section for the AtExit module:

https://metacpan.org/pod/AtExit#SEE-ALSO

I haven't check to see whether any of them handle the fork case you case.

In your make_upper_caser_iterator example, I'm pretty sure that $fh becoming unreferenced closes the file anyway. So the explicit close isn't needed (except, of course, that you could check the result and handle errors). Also, if your file handle is valid in the child process, you want to close it there too—fork copies file handles, too.

A neat trick, but I think in a lot of cases RAII handles this better as it's tied to the data involved, not just function exit. Of course, sometimes you really do want cleanup for a function.

PS: Watch out for your cleanup-objects accidentally getting sucked into closures. That could lead to very unexpected outcomes.

Leave a comment

About tnish

user-pic I do security research and hacking at a anti-spam company in SF, mostly in Perl and Go. Formerly worked in computational genomics/bioinformatics.