I am writing these in view of class objects commonly being constructed as GC-owned objects.

GUIDELINES:

We've seen the following points mentioned in these forums.

- (This one is just a complication.) Classes don't have destructors anyway; they should be called finalizers.

- Don't allocate memory from the GC in a class destructor. (For example, writeln may be fine with simple types but writefln or format would not be.)

- Don't touch any GC-owned member in a class destructor because that member may have already been finalized.

SOME FACTS:

- Class destructors are not guarenteed to be executed. And this cannot be controlled by the programmer. For example, the user of the program can pass the following command line option and the GC will not execute class destructors:

  $ myprogram "--DRT-gcopt=cleanup:none" ...

(The following example will run fine when started that way.)

- Even if the destructor is executed by the GC, when it is executed exactly is naturally unspecified ("sometime in the future"). For that reason, it does not make sense to leave important responsibilities to class destructors like closing a File. Not only the file may not be closed, you may be exceeding resources that the OS provides by holding on to them for too long.

HORROR:

Now, a big one that I've just realized.

- The two guidelines above (the "don't touch class members" one and the "don't allocate" one) miss an important fact that they apply recursively even to struct members of classes. For example, structs that are used as members of a class cannot allocate memory in their destructors either.

The following program ends with core.exception.InvalidMemoryOperationError:

import std.format;

void closeConnection(string s) {
}

struct S {
  int id;

  ~this() {
    closeConnection(format!"%s signing off"(id));
  }
}

class C {
  S s;

  this(int id) {
    s = S(id);
  }

  // @disable ~this();
}

void main() {
  // Just 1 is sufficient to show the error in Ali's environment.
  enum N = 1;
  foreach (i; 0 .. N) {
    auto c = new C(i);
  }
}

Note how the struct is written in good faith and the class is obeying all the guidelines (by not even defining a destructor). I think the problem is the missed guideline that is on the subject line: classes should @disable their destructors. The program above will work fine when that line is uncommented.

Of course, we still should and do have the power to shape our programs any way we want but I think '@disable ~this();' should be added to classes as a general rule unless the programmer knows it will work otherwise.

What do you think?

Ali

Reply via email to