Destructor attribute inheritance, yea or nay?

Stanislav Blinov via Digitalmars-d digitalmars-d at puremagic.com
Mon May 22 10:05:06 PDT 2017


I'd like to hear what you guys think about this issue:

https://issues.dlang.org/show_bug.cgi?id=15246

Marco argues that because "it currently doesn't work that way" 
(i.e. destructors are not inherited), the bug is invalid.

However, what this means in practice is:

- destroy()/rt_finalize() can never be anything but @system
- destructors of derived classes, and even destructors of 
aggregates (structs) can violate attributes, and the compiler 
does nothing to prevent that

Considering that the core runtime component - the GC - is the one 
that usually handles finalization, it follows that *GC collection 
can never be @safe*. And since collection only happens during 
allocation, it follows that allocation cannot be @safe either. 
Nor can they be @trusted, because destructors are effectively not 
restricted in any way. IOW, the "doesn't work that way" claim 
effectively hammers shut the coffin of memory safety as far as 
dynamic allocation is concerned, and that means the whole runtime 
and anything that depends on it.

I am of the opinion that the destructors should not be capable of 
violating the aggregated destruction attributes. This would allow 
the destroy() function to safely infer the correct attribute set 
for finalization, and propagate it to the calling code.

I.e. we could implement destroy() for classes as follows:

>void destroy(T)(T obj) if (is(T == class))
>{
>    (cast(_finalizeType!T)&rt_finalize)(cast(void*)obj);
>}
>
>void destroy(T)(T obj) if (is(T == interface))
>{
>    destroy(cast(Object)obj);
>}

>extern(C) void rt_finalize(void* p, bool det = true);

>extern(C)
>template _finalizeType(T)
>{
>    static if (is(T == Object))
>    {
>        alias _finalizeType = typeof(&rt_finalize);
>    }
>    else
>    {
>         alias _finalizeType = typeof((void* p, bool det = true) 
> {
>            // generate a body that calls all the destructors in 
> the chain,
>            // compiler should infer the intersection of 
> attributes
>            // _Seq is an equivalent of std.meta.AliasSeq
>            // _Bases is an equivalent of 
> std.traits.BaseClassesTuple
>            foreach (B; _Seq!(T, _Bases!T)) {
>                // __dtor, i.e. B.~this
>                static if (__traits(hasMember, B, "__dtor"))
>                    () { B obj; obj.__dtor; } ();
>                // __xdtor, i.e. dtors for all RAII members
>                static if (__traits(hasMember, B, "__xdtor"))
>                    () { B obj; obj.__xdtor; } ();
>            }
>        });
>    }
>}

This would keep the inferred attributes for code that actually 
calls destroy(). However, currently we cannot do that, because 
the language does not enforce attribute propagation in 
destructors, and at runtime, destroy() could be called via base 
class reference, while derived class violates the attributes:

class Base {
     ~this() @safe @nogc {}
}

class Derived : Base {
     ~this() {}
}

Base b = new Derived;
destroy(b); // infer @safe @nogc, while in reality this call is 
neither @safe nor @nogc, it is @system

Any thoughts?


More information about the Digitalmars-d mailing list