Double-Load Guards in Ruby

If you’ve ever worked with C or C++ you no doubt remember that one of continual headaches of working with those languages is avoiding double-inclusions of header files. Most C headers start and end with preprocessor directives in order to avoid this scenario:

At first the situation in Ruby seems much improved. We have require, after all, which ensures that a given file will only be loaded once. Or does it?

As it turns out all is not rosy in Ruby-land. Require works great for gems and system libraries. But when we start loading files relative to the current file, the old double-load problem rears it’s ugly head once more.

Let’s take a look at why this happens. First, we’ll define a file to load:

Now we’ll define a client file which requires foo.rb:

When we run the script above, we get the following output:

Of course, we’d never write a file like that, with the same library being required twice using two different paths. But in larger projects it is all too common for a file to be required using a different path in different files. Because Ruby does not use canonicalized pathnames to check if it has already loaded a file, it assumes that the different paths must refer to different files and loads the file over and over again.

Is this a problem? Besides for slower application startup, the most common ill effect of repeated file loads is constant redefinition warnings. If you have a project that outputs a lot of warnings that look like this on startup…

…you probably have some files being loaded twice or more times.

More serious and subtle side-effects of double-loading can occur though, especially if the files being reloaded do any class-level metaprogramming. Errors caused by double-loading can be strange and very difficult to track down.

So what do do? Well, first we need to find where the offending loads are originating from. In a large project this can be a daunting task. Here’s some code I wrote to help track down double loads at Devver:

This code should be loaded as early as possible in your project. Once loaded, it spits out some a very noisy warning every time a file is re-loaded using a different path:

But how do we avoid reloads, once we have located the offenders? The simplest remedy is to always expand relative paths before requiring them. I prefer to use the two-argument form of File.expand() to construct fully-qualified paths:

Eliminate your double-loads, and your Ruby code will load faster, produce fewer warnings, and be that much less prone to bugs.