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