On 5/22/18 9:59 PM, sarn wrote:
(I'm referring to Scott's 2014 DConf talk: https://www.youtube.com/watch?v=KAWA1DuvCnQ)

I was actually preparing a DIP about this when Manu posted to the forums about his own related problems with C++ interop.

I traced a bug in some of my D code to my own misunderstanding of how D's destructors actually work.  So I did some research and discovered a bunch of edge cases with using __dtor, __xdtor and hasElaborateDestructor.  I tried reviewing the packages on code.dlang.org and some elsewhere (thankfully only about 1% of D packages use these functions directly) and it turns out I'm not the only one confused.  I don't have time to file bug reports for every package, so, if you're responsible for code that handles destructors manually, please do a review.  There's a *lot* of buggy code out there.


I think it's very good to bring attention to these pitfalls, thanks!


Here are some recommendations:
* Really try to just use destroy().  Manually working with __dtor/__xdtor is a minefield, and I haven't seen any code that actually reimplements the RTTI walk that the runtime library does.

This is advice you need to follow. Using the underlying __functions are error prone, and subject to weird errors as you have found.

The other thing to do is to follow the example of what Phobos functions do when dealing with these low-level functions.

In terms of classes, the RTTI walk is callable via the function _rt_finalize (an extern(C) function, you can prototype it anywhere). I highly recommend just calling destroy as it will do this.

(Unfortunately destroy() currently isn't zero-overhead for plain old data structs because it's based on RTTI, but at least it works.)

Hm.. that should be fixed. I don't see why we can't just do = T.init, we should at least be optimizing to this for small enough structs.

Please file an enhancement request.

* Avoid trying to figure out if something needs destruction. Just destroy everything, or make it clear you don't own classes at all, or be totally sure you're working with plain old data structs.

Ideally, destroy should do the most efficient thing. So this is good advice as well.

* Some code uses __dtor as a way to manually run cleanup code on an object that will be used again.  Putting this cleanup code into a normal method will cause fewer headaches.

Using __dtor is a very bad idea in almost all cases. Putting cleanup code into a normal function can have some of the same pitfalls, however (what if you forget to call the super version of the method?). The only *correct* way to destroy an object is to follow the runtime's example or call the function directly.

The destructor also has the nice feature of being called when the struct goes out of scope.

Best advice -- just use destroy on types to clean them up.


The one other usage of these low-level destructor facilities is checking if a type is a plain old data struct.  This is an important special case for some code, but everyone seems to do the check a different way. Maybe a special isPod trait is needed.


isPod as you have described it is not difficult:

enum isPod(T) = is(T == struct) && !hasElaborateDestructor!T;

But this is not necessarily the definition of POD. Generally this means it has no postblit, and some people may even be expecting such a thing to have no methods as well. So I'm not sure we want to add such a definition to the library.

Clearly destroy is preferred to checking or doing anything else, but maybe in some cases we want a more efficient destruction that cuts corners. For instance, there's no reason to blit the init value back to the object when its memory is being reclaimed. That building block is somewhat inextricable from destroy at the moment.

Ideally, you shouldn't have to worry about it as a normal user -- some genius library author should take care of these details. Unfortunately, the only place where this happens is on the stack or in the GC. All other memory allocation schemes require you to get your hands dirty with destruction.

-Steve

Reply via email to