August 03, 2018
On Saturday, August 04, 2018 03:25:12 12345swordy via Digitalmars-d wrote:
> On Saturday, 4 August 2018 at 02:21:48 UTC, 12345swordy wrote:
> > Are you telling me that D is incapable of determining the classes that is currently inheriting the parent class? That not
>
> *Create a list of child classes that is currently inheriting the parent class.

D is not capable of giving you the list of classes that are derived from a particular class. Stuff like separate compilation makes that not work.

It can tell you what the base classes are for a particular class, which is what I would think you would need for stuff related the attributes that a destructor/finalizer arguably should have (e.g. if D actually had attribute inheritance for destructors), but it can't give you a list of classes derived from a particular class. If you need that for some reason, then you're out of luck.

- Jonathan M Davis

August 04, 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-@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-@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.
August 04, 2018
On Saturday, 4 August 2018 at 04:51:55 UTC, Adam D. Ruppe wrote:
> 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....
I know the risks behind it, @nogc and others only care that non-@nogc functions are not called. That's why I propose destructor_hook function for clear transparency.

>
> 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.
I was thinking along of lines of "Canary in a coal mine" when it comes to class static typing if the compiler couldn't determine the child classes that are currently inheriting the current non-final class. A system attribute "Canary" Boolean value can be override by compiler to set it to false if it detects a "Gas leak".

> 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....
>
You want me to introduce the "restrict" keyword in my dip for maximum safety? Fine, I don't mind.

> 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.
People are treating classes like a neglected child due to the lack of @nogc. We could solve the "is it @nogc?" issue by having the compiler provide a strict readonly body of the function/class/module (after all the meta-programming has been applied obviously) and statically analyze it, but there are fears that this will lead to AST, and I don't have enough spare time to argue all day on this atm.

-Alexander

1 2
Next ›   Last »