Managing Object Lifetimes in D
The D Programming Language is a modern version of C. It adds productivity features to the performance power of C, features like object oriented programming and garbage collection.
It may seem strange that a language with focus on performance utilizes automatic memory management. A GC equals overhead, right? Well, actually that is a common misconception. These days implicitly memory managed code is generally faster than code where the programmer handles deallocations. One reason for this counter-intuitive fact is that a number of optimizations can be done by the GC, especially on short-lived objects.
But garbage collection isn’t a problem free feature. For one thing, it is indeterministic. Normally, an object that uses external resources acquires them in a constructor and releases them in the destructor.
import std.stream;
class LogFile {
File f;
this() {
f = new File("c:\\log.txt", FileMode.Out);
}
~this() {
f.close;
}
// Logging methods goes here
}
But with a GC there is no way to know when or even if the destructor is run. This may be a problem if you’re dealing with scarce resources like window handles, or file locks. So how can we control object destructions in a GC capable language? One possibility is to trigger a GC sweep explicitly in your code. In D this is done with the fullCollect function.
import std.gc;
fullCollect();
Explicit trigging of garbage collection is usually a bad idea, for several reasons. For one thing, it’s like killing an ant with a bazooka, and still you risk missing the target. Therefore, the normal approach to deal with the problem of indeterminism is to use a dispose method.
class LogFile {
File f;
this() {
f = new File("c:\\log.txt", FileMode.Out);
}
~this() {
dispose();
}
void dispose() {
if (f !is null) {
f.close;
f = null;
}
}
// Logging methods here
}
LogFile log = new LogFile();
try {
// Do some logging
} finally {
log.dispose;
}
We don’t have to use the dispose pattern though because in D we have the luxury of choosing between implicit and explicit memory management. We can either free the object ourselves with the delete operator, or leave it for the GC to dispose later.
LogFile log = new LogFile();
try {
// Do some logging
} finally {
delete log; // Explicit memory management
}
D has another construct which is very convenient when you need to control the lifetime of an object. With the scope attribute, an object is automatically destroyed when the program leaves the scope in which the object was created.
void function some_func() {
scope LogFile log = new LogFile();
:
// log will be freed on exit
}
The ability to mix explicit and implicit memory management is a simple, yet great feature. It is one of many examples where D provides us with the best of two worlds. Convenience and control, as well as productivity and performance, is blended in a way that has no equivalence in any other language I know.
Cheers!
You’ve read my mind today! ; )
This is the only thing I don’t like of D.
I’ve been fighting with the fact that we cannot trust in the destructors of D, sometimes are called and sometimes aren’t… This breaks an essential rule in OOP.
The GC “knows” that the O.S. will deallocate the memory reserved on exit, then why worry deleting all objects?
My solution to this problem is:
1.- Never trust in D destructors, they are unsafe. Assume that the GC won’t call them. (In medium/big-sized projects in D the GC don’t call a lot of destructors!).
2.- If you have a class that needs to execute always its destructor to release something then declare it as:
scope class Foo {}
This will force you to declare as scope every instance of Foo, if you don't do it, you'll get a compiler error: "reference to scope class must be scope".
3.- When you need it, use "delete".
Cheers!
Javi
Well, I’ve come to realize that I don’t really need a deterministic destruction of objects very often. But when I do need it, the D solution to support both implicit and explicit destruction is really convenient.
Good thing you pointed out scope classes. They are great if you need to make sure the object never escapes the scope it was created in.
Ah! I forgot the 4th point…
4.- Do not trust in fullCollect(), it won’t always collect the unreferenced objects.
Can’t argue with this one 🙂
After reading this and the comments above, I am definitely at a loss for when to trust Garbage Collection and when to call delete. I understand the scope keyword, but does it always work? Argh! I like a set of rules that are never broken (don’t we all?).
BTW, I really enjoy your stuff (especially the D related articles). Very helpful for those of us that are trying to pick up the language. I’m working on a D blog as well – a tutorial based blog for beginner and intermediate D programmers.
I have this rule:
If you’ve implemented the destructor of a class then, you must always delete it yourself:
– If the instanced object is local; define it with “scope” attribute, and it’ll always be destroyed on exit of its scope.
– If the instanced object is global, static or a member of a class then you must use “delete”.
If your class doesn’t need to release any handle, etc, on exit and you’ve not implemented its destructor, then your work is done, forget it. If the GC doesn’t collect it, it’ll be collected by the O.S. when the app exits.
Thanks for your help – that clarifies things a bit. It’s going to take me a while to get good at memory management again (still not nearly as bad as C++). I was a C guy back in school, but I’ve been doing C#, Java, Perl, and TCL at work for the past 8 years! It’s nice to get back to lower level programming – I’m having a lot of fun with D.
Thank you for your nice comment. I too am “trying to pick up the language”.
I’ll keep an eye on your blog as well, especially the low-level stuff, something I haven’t done too much myself.
‘scope’ always works. Generally you use it when you need to release a resource (like closing a file) right after you’re done using it.
When an app exits, the GC calls the destructors of all objects that are still alive. But not if the app crashes, so you can’t rely on important stuff like closing files to happen that way.
No, when the app exits the GC doesn’t call the destructors of all objects because it doesn’t collect all object (no matter if the app exits without errors)… This can be true with small programs, but if you program a large app in D you’ll see what happen…
One of the reasons is that the data on the stack is scanned by the GC, if you have, for example, a simple local byte array, and some of its values look like any reference in the GC list, then that reference is not collected. And this happens!
Man you are dumb. If you don’t trust the garbage collector, then use C or C++ like everybody else.
Ohhh! What a constructive comment! Bravo!
And how do you know that I don’t use them???
Relax a bit and enjoy life man 🙂
Not only is the D garbage collector non-deterministic, but you can’t reference members in the destructor. Therefore, your first example may give a runtime error because f may have be collected before the LogFile’s destructor is called.
~this() {
f.close; // f should not be used
}
Ah, that is important information. Thank you for bringing attention to it.