www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Is it possible to deinitialize the class without calling the gc?

reply 12345swordy <alexanderheistermann gmail.com> writes:
Call me curious, but I have never anyone done this before. I 
don't mean calling the .__dtor function as you can't call its 
parent .__dtor function. Is there RecusiveDestruct function that 
can be called that is attribute friendly?

-Alexander
Aug 03 2018
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 3 August 2018 at 15:32:54 UTC, 12345swordy wrote:
 Is there RecusiveDestruct function that can be called that is 
 attribute friendly?
No, the destructor definition in the language is not attribute friendly. Best you can do is `.destroy(obj)` and it can't see all the nogc stuff (because the language doesn't actually support it here)
Aug 03 2018
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Friday, 3 August 2018 at 15:41:13 UTC, Adam D. Ruppe wrote:
 On Friday, 3 August 2018 at 15:32:54 UTC, 12345swordy wrote:
 Is there RecusiveDestruct function that can be called that is 
 attribute friendly?
No, the destructor definition in the language is not attribute friendly. Best you can do is `.destroy(obj)` and it can't see all the nogc stuff (because the language doesn't actually support it here)
Your calling the gc if you call the destroy function for classes as it calls the rt_finalize function. Regardless you didn't answer the question that I posted in the title. Alexander
Aug 03 2018
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 3 August 2018 at 16:24:33 UTC, 12345swordy wrote:
 Your calling the gc if you call the destroy function for 
 classes as it calls the rt_finalize function.
rt_finalize is not the GC. It just calls destructors in order for each base class.
 Regardless you didn't answer the question that I posted in the 
 title.
Yes, I did. The .destroy function does that, but it ignores attributes.
Aug 03 2018
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Friday, 3 August 2018 at 16:30:41 UTC, Adam D. Ruppe wrote:

 rt_finalize is not the GC.
Yes it is. That why is called finalize. https://en.wikipedia.org/wiki/Finalizer https://github.com/dlang/druntime/blob/633976f1b2619974e8b3b415e0052b38d1a8e2fb/src/gc/impl/manual/gc.d -Alex
Aug 03 2018
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 3 August 2018 at 17:50:10 UTC, 12345swordy wrote:
 rt_finalize is not the GC.
Yes it is. That why is called finalize.
OK, so you don't like it... because of its name? It doesn't actually call any GC functions, it just loops through destructors and calls them all.
Aug 03 2018
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Friday, 3 August 2018 at 18:52:13 UTC, Adam D. Ruppe wrote:

 OK, so you don't like it... because of its name?
Did you even bother reading the links that I posted? The term Finalize is commonly associated with OOP languages such as java both of them. There has been some talks about splitting them.
 It doesn't actually call any GC functions, it just loops 
 through destructors and calls them all.
Then you should know where the implementation of finalize is located at, or am I looking at the wrong code here?
Aug 03 2018
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 3 August 2018 at 20:40:31 UTC, 12345swordy wrote:
 Did you even bother reading the links that I posted?
I did. Did you? The wikipedia page speaks at length to the ambiguity of the terms, including that the language construct can be called a destructor while the implementation methods can be called a finalizer, even if they do the same thing.
 Then you should know where the implementation of finalize is 
 located at, or am I looking at the wrong code here?
It is in rt/lifetime.d. The .destroy(class) function is defined on line 560 of object.d in druntime/src (assuming code as of v2.081.1 release). This calls rt_finalize, defined on line 1422 of rt/lifetime.d (and note that there is a separate function right under it for finalizeFromGC!). This simply forwards to rt_finalize2, defined on line 1380 in the same file, which does the actual work. Observe how it simply calls the destructors, in order, of the base class instances, then deletes the monitor if it is present, and copies the initialize memory over to make that predictable, but then nulls out pointer to the rtti instance for that dead object, to ensure it is unusable. The one you linked to is totally unrelated. It doesn't even define a function called destroy nor rt_finalize. It actually defines a function to finalize the (manual) GC object itself! Its run finalizers method is supposed to detect what type of objects are in the passed memory block, and call rt_finalize (or one of its variants) for them. But, of course, since it is just a stub implementation of the GC interface, it doesn't actually do that. rt_finalize is called BY the GC or the public destroy function, but it is NOT part of the GC. It also does not call into any GC functions itself (though user-defined hooks or class destructors might - but note if a class dtor does, it is liable to cause the program to terminate with InvalidMemoryOperatorError in certain circumstances). It is also called by the language almost directly in the case of `scope` classes, via the `_d_callfinalizer` function (defined on line 1238 of rt/lifetime.d, where you can see it is a simple forward to rt_finalize). scope classes are allocated and destroyed just like structs - they live on the stack and their destructors are called deterministically when they go out of scope. Explicitly not GC'd... but still calling the same rt_finalize function. BTW: Generally speaking, the calls to _d_xxxx functions are emitted by the compiler itself, then they forward to _rt_xxx functions, which are the internal runtime implementations, and those _rt_xxx functions are also accessible through other public interfaces, like the .destroy() function, or Runtime.xxx static struct methods, etc., to give them a prettier name. Those prettier names are the documented ways to access it, whereas the _rt_ ones are subject to change without notice by druntime devs and the _d_ ones are subject to change by dmd devs. So, again:
 Is it possible to deinitialize the class without calling the gc?
Yes, use the .destroy function, or a scope class (or the scoped! template in std.typecons, which tried to replace the built-in scope thing due to memory safety concerns at one point).
 Is there RecusiveDestruct function that can be called that is 
 attribute friendly?
No. Best you have is the .destroy function, which cannot work with the attributes because the D language definition does not make that possible. Child classes have independent destructors which do not need to adhere to the attributes of the parent class destructor, and moreover this cannot be detected at compile time when the attributes are processed - yet both must be called. Thus, at compile time, the destruction process must assume the widest possible features and the attributes do not apply.
Aug 03 2018
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Friday, 3 August 2018 at 21:44:55 UTC, Adam D. Ruppe wrote:
 Is it possible to deinitialize the class without calling the 
 gc?
Yes, use the .destroy function,
Which is converted to void type when passing the object to rt_finalize, which causes to lost all type information. How is this a better solution!?
 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. There is no reason for a child class to adhere to the attribute of an empty destructor. If you want to be transparent of the possibility of them not being called, then create "destructor_hook".
 and moreover this cannot be detected at compile time when the 
 attributes are processed
Are you telling me that D is incapable of determining the classes that is currently inheriting the parent class? That not even extern (D) can provide information to the compiler!? Do I need to add meta information section to my DIP? I am not interested in a poor man solution here. This is a serious problem that needs addressing. I literally see the usage of __.dtor in the standard library! What's worst is that I seen sledge hammer approach to this by forcing the destroy function to be nogc! I am not convinced that the upcoming protoObject is going to fix this issue given that old Object still exist.
Aug 03 2018
next sibling parent reply 12345swordy <alexanderheistermann gmail.com> writes:
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. -Alexander
Aug 03 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Aug 03 2018
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
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.
Aug 03 2018
parent 12345swordy <alexanderheistermann gmail.com> writes:
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
Aug 04 2018