The Open-Closure-Close Idiom in D
In a Reddit discussion following my last post on object lifetime management in D, bonzinip wondered why the D closures aren’t used with the open-closure-close idiom that is common, for instance, in Ruby.
bonzinip: languages with closures (Ruby, Smalltalk) use them to ensure that the file is closed when you get out of scope, and that does not require new keywords and does not require to bypass GC and possibly get dangling pointers.
NovaProspekt: D actually has full closures
bonzinip: so why don’t they use them with a “try { open; run closure; } finally { close; }” idiom instead?
Well, let’s take a look at how that could be accomplished in D.
To get an image of the open-closure-close idiom, we can look at Ruby’s File class, and particularly the open method.
IO.open(fd, mode_string=”r” ) => io
IO.open(fd, mode_string=”r” ) {|io| block } => objWith no associated block, open is a synonym for IO::new. If the optional code block is given, it will be passed io as an argument, and the IO object will automatically be closed when the block terminates. In this instance, IO::open returns the value of the block.
What this means is that a File can be opened, either in the normal way with explicit closing (begin-ensure corresponds to try-finally in D)
f = File.open("c:\\log.txt", "w")
begin
f.puts "Log this"
ensure
f.close
end
or with a code block (closure)
File.open("c:\\log.txt", "w") do |f|
f.puts "Log this"
end
In this last example the file is both opened and closed by the open method, in between the method invokes the associated code block with the file object reference as a parameter (f). In essence, that is the open-closure-close idiom.
How could we do this in D? Well, as I’ve written before, D’s delegates in combination with function literals can be used to mimic the code blocks of Ruby. So, a D version of the File::open method could look something like this:
import std.stdio;
import std.stream;
void open_run_close(char[] fn, FileMode fm = FileMode.In,
void delegate(Stream) closure)
{
Stream f = new File(fn, fm);
try {
closure(f);
} finally {
f.close();
}
}
and be invoked like this:
open_run_close("c:\\log.txt", FileMode.Out, (Stream f) {
OutputStream os = f;
os.writefln("Log this");
});
To make it a little more like the original Ruby version, let’s make it support the non-closure variant as well.
Stream open_run_close(char[] fn, FileMode fm = FileMode.In,
void delegate(Stream) closure = null)
{
Stream f = new File(fn, fm);
if (!closure) {
return f;
} else {
try {
closure(f);
return null;
} finally {
f.close();
}
}
}
// Example usage
open("c:\\log.txt", FileMode.Out, (Stream f) {
OutputStream os = f;
os.writefln("Log this");
});
// Example traditional usage
OutputStream f = open("c:\\log2.txt", FileMode.Out);
try {
f.writefln("Do log this");
} finally {
f.close;
}
The open-closure-close idiom is definitely doable in D. It’s not as pretty and natural as in Ruby, but not so far from it either. Whether this particular idiom will be embraced by the D community is yet to be seen.
Cheers!
The D language has a “scope” keyword, as mentioned in the documentation. Basicall instead of all that jazz from the article, simply:
void doSomething()
{
scope f = new File(“/log.txt’, …);
/* do work */
}
void main()
{
doSomething();
/* f is guaranteed by specification not to exist by
this point, regardless of exceptions. */
}
Indeed, scope is a great feature. For some reason I seem to be reluctant to use it. I’m sure that’s just because, as we say in Sweden, “it’s difficult to learn old dogs to sit.”
On the other hand, it’s easier to get an ol’ dog to sit, when you provide a warm, cozy pillow under his bottom. Think of scope as your warm, cozy pillow. 😉
I think this looks way neater without try {} finally {}, but rather the oh-so-bad “new” syntax scope(exit):
Stream open(char[] fn, FileMode fm = FileMode.In,
void delegate(Stream) closure = null)
{
Stream f = new File(fn, fm);
if (closure !is null) {
scope(exit) f.close;
return closure(f);
} else return f;
}
// Example usage
r"C:\log.txt".open(FileMode.Out, (Stream f) {
OutputStream os = f;
os.writefln("Log this");
});
I’m bound to agree. That is a nicer looking implementation.