Factor Language Blog

Generic resource disposal

Thursday, January 31, 2008

One approach is the Java approach. It is pretty much the worst:

Resource r = acquire(...);

try {
    doSomething(r);
} finally {
    r.dispose();
}

This is bad because it leads to code duplication, and forgetting to pair acquisition with disposal, or forgetting to do it with try/finally, can result in resource leaks which are hard to track down.

Idiomatic C++ combines resource acquisition with memory management, so you have something like:

void foo(...) {
    Resource r;

    doSomething(&r);

    /* r's dtor called here */
}

This is somewhat cleaner than the Java approach but it depends on value types and deterministic memory management, which is not a trait that most high-level languages share.

Languages with first-class functions such as Lisp, Haskell and Factor take another approach, which is my personal favorite; you pass a block of code to a higher order function, which encapsulates the try/finally boilerplate for you:

[ do-something ] with-stream

This approach works very well; it composes naturally for acquiring multiple resources at once, and there is no boilerplate on the caller side.

However, with our growing library we’re also growing the set of disposable resources. Right now, we have:

  • Streams
  • Memory mapped files
  • File system change monitors
  • Database connections
  • Database queries

With more on the way. This creates a problem because each with-foo word does essentially the same thing:

: with-foo ( foo quot -- )
    over [ close-foo ] curry [ ] cleanup ; inline

There were some variations on the theme, but it was all essentially the same.

Instead of having different words to close each type of resource, it makes sense to have a single word which does it. So now the continuations vocabulary has two new words, dispose and with-disposal. The dispose word is generic, and it replaces stream-close, close-mapped-file, close-monitor, cleanup-statement (databases), etc. The with-disposal word is the cleanup combinator.

The with-stream word is there, it binds stdio in addition to doing the cleanup. However it is simpler because it now uses with-disposal.

Simple and sweet, and now all the boilerplate is gone, on the side of the caller as well as the implementation side.