Is it possible to deinitialize the class without calling the gc?

Adam D. Ruppe destructionator at gmail.com
Sat Aug 4 04:51:55 UTC 2018


On Saturday, 4 August 2018 at 02:21:48 UTC, 12345swordy wrote:
> Which is converted to void type when passing the object to 
> rt_finalize, which causes to lost all type information.

It still has run-time type information, but indeed, the compile 
time info is lost.


>> Child classes have independent destructors which do not need 
>> to adhere to the attributes of the parent class destructor,
> Why is this an issue? Simply don't call them.

Then the object isn't fully destroyed....

scope BaseClass c = new DerivedClass();

if that only destroys BaseClass' members, you have some serious 
leakage on whatever DerivedClass happened to add to it.

Remember, that code is always legal, and the actual type of c may 
not be known until runtime:

BaseClass c = (readln() == "lol\n") ? new DerivedClass : new 
OtherClass; // legal code!

Or, of course, destruction of the base is important too:

scope DerivedClass c = new DerivedClass();

if that leaves BaseClass' members hanging, again, serious leakage.


You could redefine the language so each child class's destructor 
is also responsible for cleaning up the base class explicitly... 
but that'd be really ugly to write reliable OO code with since it 
wouldn't actually inherit cleanup behavior. Really, the child 
classes need to call the base class destructor, if not 
implicitly, it needs to be required explicitly somehow.

> How is this a better solution!?

It is the ONLY solution right now. If you're writing a DIP, you 
might be able to change that, but first you need to understand 
the current situation and the reasoning behind it.

If you are looking for a @nogc destroy function, the language 
MUST change, and this isn't as simple as many people think it is. 
Specifically, destructors will have to start acting like other 
virtual methods, with mandatory attribute inheritance or 
tightening, but never loosening.. yet also keeping the hidden 
calls to the super function.

Let me show a few examples.

---
class A {
    @nogc ~this() {}
}

class B : A {
    ~this() {} // MUST become an error: non- at nogc destructor 
cannot "override" @nogc parent destructor
}
---

This part is the same as ordinary method inheritance in D (but 
see below).

Once that's in place, it would become possible for .destroy to 
take it argument by template and act on the @nogc compile time 
attribute. But note:

---
A a = new B();
destroy(a); // would NOT be @nogc since the knowledge that it is 
a B is lost here, due to separate compilation requirements
---


And, moreover, if the child destructor is non-trivial, adding to 
it even with the strengthening restriction is impossible - I put 
scare quotes around "override" above because it doesn't actually 
override, it stacks the functions.

---
class A {
     ~this() { /* non-trivial destructor present */ }
}

class B {
     @nogc ~this() {} // Error: @nogc B.~this must call A.~this to 
clean up, but can not because A.~this is not @nogc
}
---


This is *different* than ordinary method inheritance in D. 
Normally, the child class method is allowed to override and 
tighten the interface. But with destructors, it MUST call super 
to avoid resource leaks (and in D, does so implicitly), so it is 
also sticking to the wider interface.



Now, here's where it gets really crazy. A class' destructor can 
be non-trivial if ANY of its members have a non-trivial 
destructor:

---
struct S {
     ~this() {}
}

class A {
     S s;
}

class B : A {
    @nogc ~this() {} // guess what? Error: @nogc B.~this cannot 
call A.~this because it calls non- at nogc function S.~this.
}
---

Both the call to A.~this and the very existence of A.~this and 
the fact it calls S.~this are implicit... but both are important 
to ensure that struct is properly cleaned up when the time comes.



Now, what about this?

---
class A {} // note that there is no destructor

@nogc void main() {
    A a = getSomeA();
    destroy(a); // is this legal?
}
---


The only correct answer is: no, that is not legal. A itself has 
no destructor, so calling it BY ITSELF would be @nogc compatible.

But, you have no idea what child class you actually have there. 
`getSomeA()` might have returned:

---
class B : A {
     ~this() { /* lol ima call the GC */ }
}
---

Since A has no destructor, that child is legal; A did not 
prohibit the GC call. (And if it did, that would be fairly 
annoying, though it is a valid argument to make - to say that ALL 
destructors must be @nogc, especially since the status quo 
includes InvalidMemoryOperationError. But... it hits the 
false-positive pain of @nogc, and can break currently-valid code 
that calls the GC in a correct manner to avoid the invalid memory 
operation errors.)

But if that child is legal, the destroy function MUST assume it 
might get one of those, and thus make no compile-time guarantees.

So, even with the changes I outlined above, @nogc destroy would 
be valid if and only if it is passed a class whose *static* type 
explicitly includes the @nogc guarantee, which is only allowed if 
all the parent chains, all the way up, have either trivial 
destructors or their own @nogc guarantees.


> Do I need to add meta information section to my DIP?

We already know the information at run time. But not at compile 
time since subclasses may be compiled totally separately to base 
classes. (They may even reside in totally separate DLLs and 
change based on runtime decisions, like what plugins the user 
actually has installed on their computer, or like my little 
example above, you could make it depend on user input.)


But you can derive the bits you need from compile time info if 
the child destructors were restricted as described above. Unless 
I'm missing something too....

Just there's a bunch of cases here that the current language has 
no solution for.


of course you could just do the sane thing and pretend @nogc 
doesn't exist. then this stuff becomes a lot easier and you can 
just get back to work.


More information about the Digitalmars-d mailing list