Defend your code with Guard

I can’t always trust my subroutines to leave the world in the same way that they found it. Perl has some features to help with this, but the Guard module goes much further.

Consider the case where I want to change the current working directory temporarily in my subroutine. If I’m not careful, the rest of the ends up in an unexpected directory since chdir has process-level effect:

sub do_some_work {
  state $dir = '/usr/local/etc';
  chdir $dir or die "Could not change to $dir! $!";
  
  ...; # do some work
}

Since I don’t change back to the starting directory, after I call do_some_work, the rest of the program uses /usr/local/etc as the base to resolve relative paths.

If I were careful, I would have done the work to save the current working directory before I changed it, and I would have changed back to that directory. The getcw from the Cwd module from the Standard Library:

use Cwd qw(getcwd);

sub do_some_work {
  state $dir = '/usr/local/etc';
  
  my $old_directory = getcwd();
  chdir $dir or die "Could not change to $dir! $!";
  
  ...; # do some work
  
  chdir $old_directory 
    or die "Could not change back to $old_directory! $!";
    
  return $value;
}

That’s too much work. I have long wished that the chdir would return the old directory like select returns the current default filehandle. Instead, I use a module with an imported subroutine.

I also have to call another chdir when I’m done, and I probably have to add some extra code to return the right value since I can’t easily organize the code to use Perl’s nifty last-evaluated-expression idiom (although Perl 5.20 optimizes return at the end of a subroutine). It offends my sense of code style that the two chdirs are apart from each other when I want to keep the logical parts close to each other. I’d like all of the code to handle the current working directory next to each other.

Enter the Guard module that lets me define blocks of code that run at the end of the subroutine. Somewhere in the scope I create a guard with scope_guard and that guard runs at scope exit:

use v5.10;

use Cwd qw(getcwd);
use Guard;

chdir '/etc' or die "Could not start at /etc: $!";
my $starting_dir = getcwd();

do_some_work();

say "Finally, the directory is ", getcwd();


sub do_some_work {
  state $dir = '/usr/local/etc';
  
  my $old_directory = getcwd();
  scope_guard { 
    say "Guard thinks old directory is $old_directory";
    chdir $old_directory;
  };
  chdir $dir or die "Could not change to $dir! $!";
  
  say "At the end of do_some_work(), the directory is ", getcwd();
}

The output shows which each part thinks the current working directory should be:

At the end of do_some_work(), the directory is /usr/local/etc
Guard thinks old directory is /etc
Finally, the directory is /etc

This is still a little bit ugly. The scope_guard only takes a block or sub {} argument, so I can’t refactor its argument into a subroutine. This doesn’t work:

scope_guard make_sub_ref();  # wrong sort of argument

I can make a guard in a variable, though, to get around this. Instead of doing its work at scope exit, the variable guard does its work when it’s cleaned up (which we might do on our own before the end of its scope). In this example, I use Perl v5.20 subroutine signatures just because I can (they are really nice even if they are experimental):

use v5.20;
use feature qw(signatures);
no warnings qw(experimental::signatures);

use Cwd qw(getcwd);
use Guard;

chdir '/etc' or die "Could not start at /etc: $!";
my $starting_dir = getcwd();

do_some_work();

say "Finally, the directory is ", getcwd();


sub do_some_work {
  state $dir = '/usr/local/etc';
  
  my $guard = make_guard( getcwd() );
  chdir $dir or die "Could not change to $dir! $!";
  
  say "At the end of do_some_work(), the directory is ", getcwd();
}

sub make_guard ( $old_directory ) {
  return guard {
    say "Guard thinks old directory is $old_directory";
    chdir $old_directory;  
  };
}

Now the code in do_some_work is a bit nicer and I can reuse this guard in other subroutines.

Here’s a bonus trick and one of the reasons I wanted to show the subroutine signatures. I can declare a default value for a subroutine argument. If I don’t specify an argument to make_guard, Perl fills it in with the value of getcwd

:

sub make_guard ( $old_directory = getcwd() ) {
  return guard {
    say "Guard thinks old directory is $old_directory";
    chdir $old_directory;  
    };
}

With the default value, I can simplify my call to make_guard while still having the flexibility to supply an argument:

my $guard = make_guard();

There are other tricks I can employ with M. I can define multiple scope_guards. In that case, they execute in reverse order of their definition (like END blocks). With a guard object, I can cancel the guard if I decide I don’t want it any longer.

Cover image © Kenny Loule

Tags

brian d foy

brian d foy is a Perl trainer and writer. He’s the author of Mastering Perl and co-author of Programming Perl, Learning Perl, Intermediate Perl and Effective Perl Programming.

Browse their articles