D's Destructors are What Scott Meyers Warned Us About

Steven Schveighoffer schveiguy at yahoo.com
Wed May 23 13:12:57 UTC 2018


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


More information about the Digitalmars-d mailing list