Destructors

The Go language does not have destructors. Instead, it has two more dynamic mechanisms. A defer statement may be used to run a function on function exit or when processing a panic. A finalizer may be used to run a function when the garbage collector finds that a block of memory has nothing pointing to it and can be released. Both approaches are dynamic, in that you have to executed the defer statement or call the runtime.SetFinalizer function. They are have no lexical scoping; a single defer statement in a loop can cause its argument to be called many times on function exit.

These ideas are significantly different from destructors, which are associated with a type, and are executed when an object of that type goes out of lexical scope or is explicitly deleted. Destructors are primarily used to release resources acquired by an object of the type. This is a less important concept in a garbage collected language like Go.

The absence of destructors means that Go does not support the RAII pattern, in which an object is used to acquire a mutex or some other resource for the scope of a lexical block. Implementing this in Go requires two statements: one to acquire the mutex, and a defer statement to release the mutex on function exit. Because deferred functions are run on function exit, the mapping is not exact; you can not use this technique to acquire a lock in a loop. In fact, acquiring a mutex in a loop and correctly releasing it when a panic occurs is rather difficult in Go; fortunately it is easy to handle correctly by moving the body of the loop to a separate function. In any case, Go discourages this type of programming. Mutexes are available in Go, but channels are the preferred mechanism for synchronization.

Are defer statements and finalizers sufficient replacement for destructors in a garbage collected language? They are for me. When I write C++ my destructors are almost entirely concerned with releasing memory. In fact, in the gold linker I often deliberately omitted destructors, because many of the data structures live for the life the program; in such a case, destructors serve only to slow down program exit. I would be interested to hear of a pattern of programming which relies on destructors for cases other than releasing memory or RAII.

5 Comments »

  1. jyasskin said,

    May 18, 2010 @ 8:28 am

    Grepping for __exit__ in the python standard library finds it used for: mutexes, file closing, weakref-set’s iterator (to keep removed members of the set alive), calendar (to set a different locale temporarily), decimal (to set a new context), ftp autoclosing, runpy (for temporary modules), tar-files, temporary files, unittests (to save the environment, clear out a module temporarily, set environment variables temporarily, set the path temporarily, to translate exceptions, to test for exceptions), warnings (to change their behavior temporarily), and sqlite database transactions.

    Python’s context managers (http://docs.python.org/py3k/reference/compound_stmts.html#the-with-statement) work better for dynamic languages than C++’s destructors. D has both forms of “do this at scope exit” behavior.

  2. Ian Lance Taylor said,

    May 18, 2010 @ 7:52 pm

    Thanks for the enumeration. A lot of those uses sound like cases where some dynamically scoped property is set for the duration of a function. I personally would almost never write such a case in C++, as I very rarely used dynamically scoped properties—I use some sort of environment handle instead.

    I don’t know if setting up an object to do that kind of thing is easier or harder than using something like Go’s defer statement.

  3. DGentry said,

    May 18, 2010 @ 9:59 pm

    boost::statechart implements a state machine using a class for each state. The constructor handles the entry actions when entering the new state, the destructor implements the exit actions.

    Its an interesting pattern, and nice in that boost handles nesting of substates using member objects. Having the code in the constructor and destructor does limit the error handling which can be implemented, you really can only use exceptions. That part isn’t so nice.

    There are also, of course, numerous ways to implement state machines which don’t involve destructors.

  4. ariels said,

    June 14, 2010 @ 4:25 am

    This is not quite a form of RAII: (Easy) scoped X.

    A recent example from my work: scoped timing. A macro to add a log message for how long a scope takes. By adding the macro I get the time measurement, in the log. Because it is so easy to do, (I hope) everyone will time everything. It has to be easy to do or they won’t. It can be at a low logging level.

  5. Ian Lance Taylor said,

    June 14, 2010 @ 10:04 am

    In Go you would do timing by putting something like this as the top of your function:

    defer (StartTimer())()

    Then you arrange to have StartTimer return a function which stops the timer. So it’s fairly convenient, but it’s not as convenient as possible. And it only works at function scope; to time smaller scopes you need to wrap them in a function literal.

RSS feed for comments on this post · TrackBack URI

You must be logged in to post a comment.