www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Destructors vs. Finalizers

reply Mike Parker <aldacron gmail.com> writes:
Regarding the issue with `destroy` not being  nogc, I my 
understanding is it comes down to `rt_finalize` not being  nogc. 
I haven't dug too deeply into the discussions around it, but I'm 
wondering if it's possible to separate the concept of destruction 
from finalization in the implementation?

Externally, we can do it with the existing language:

class {
     ~this() {}     // Finalizer

     ~this  nogc {} // Destructor
}

Internally, the runtime will treat each differently. an 
rt_destruct would call all every __dtor in a hierarchy and 
rt_finalize would be changed to call every __finalizer (a new 
addition) in a hierarchy. When cleaning up, the GC will ensure 
that all destructors are run where they exist, followed by all 
finalizers. And destroy would be changed to call rt_destruct 
instead of rt_finalize.

Thoughts?
Jul 25 2017
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Wednesday, 26 July 2017 at 02:58:00 UTC, Mike Parker wrote:
 Internally, the runtime will treat each differently. an 
 rt_destruct would call all every __dtor in a hierarchy and 
 rt_finalize would be changed to call every __finalizer (a new 
 addition) in a hierarchy. When cleaning up, the GC will ensure 
 that all destructors are run where they exist, followed by all 
 finalizers. And destroy would be changed to call rt_destruct 
 instead of rt_finalize.

 Thoughts?
And an important bit I left out -- destroy would still call rt_finalize if no destructor is present, which would preserve current behavior.
Jul 25 2017
prev sibling next sibling parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Wednesday, 26 July 2017 at 02:58:00 UTC, Mike Parker wrote:
 Regarding the issue with `destroy` not being  nogc, I my 
 understanding is it comes down to `rt_finalize` not being 
  nogc. I haven't dug too deeply into the discussions around it, 
 but I'm wondering if it's possible to separate the concept of 
 destruction from finalization in the implementation?
Possible, yes, and I agree that separating destruction (deterministic end of object lifetime) and finalization (GC collection caused end of object lifetime) for classes is something we should do.
 Externally, we can do it with the existing language:

 class {
     ~this() {}     // Finalizer

     ~this  nogc {} // Destructor
 }
As class destructors (in contrast to class finalizers) are then called exclusively in a deterministic fashion, there's no reason to forbid them from allocating using the GC, so I don't think using the nogc attribute would be appropriate; I would much rather see another attribute in the likes of disable, e.g. deterministic, so --- ~this() {} // Finalizer ~this() nogc {} // Finalizer ~this deterministic {} // Destructor ~this nogc deterministic {} // Destructor } ---
 Internally, the runtime will treat each differently. an 
 rt_destruct would call all every __dtor in a hierarchy
As long as finalizers are then not part of __dtor.
 and rt_finalize would be changed to call every __finalizer (a 
 new addition) in a hierarchy.
 When cleaning up, the GC will ensure that all destructors are 
 run where they exist, followed by all finalizers.
Having the GC directly call destructors defeats the point of separating them from finalizers in the first place: If a destructor should be run on GC finalication, the finalizer must manually call the destructor (using e.g. `destroy`). The GC must *never* call destructors automatically after splitting off finalizers, because that would turn destructors back into finalizers.
 And destroy would be changed to call rt_destruct instead of 
 rt_finalize.
Yes, that would then be the correct behaviour for `destroy`.
Jul 26 2017
parent reply Mike Parker <aldacron gmail.com> writes:
On Wednesday, 26 July 2017 at 09:29:15 UTC, Moritz Maxeiner wrote:


 As class destructors (in contrast to class finalizers) are then 
 called exclusively in a deterministic fashion, there's no 
 reason to forbid them from allocating using the GC, so I don't 
 think using the  nogc attribute would be appropriate; I would 
 much rather see another attribute in the likes of  disable, 
 e.g.  deterministic, so
 ---
 ~this() {}                    // Finalizer
 ~this()  nogc {}              // Finalizer
 ~this  deterministic {}       // Destructor
 ~this  nogc  deterministic {} // Destructor
 }
Yeah, this brings with it more flexibility. I'd prefer to avoid adding a new attribute for it, but this looks more interesting.
 When cleaning up, the GC will ensure that all destructors are 
 run where they exist, followed by all finalizers.
Having the GC directly call destructors defeats the point of separating them from finalizers in the first place:
Indeed! Let's pretend I didn't write that.
Jul 26 2017
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 26 July 2017 at 12:55:17 UTC, Mike Parker wrote:
 ---
 ~this() {}                    // Finalizer
 ~this()  nogc {}              // Finalizer
 ~this  deterministic {}       // Destructor
 ~this  nogc  deterministic {} // Destructor
 }
Yeah, this brings with it more flexibility. I'd prefer to avoid adding a new attribute for it, but this looks more interesting.
Some other options: ~~this() {} !this() {} !~this() {} this!(true) () {} //not really a big fan of this version
Jul 26 2017
parent Daniel Kozak via Digitalmars-d <digitalmars-d puremagic.com> writes:
 delete() {}
delete() {}

On Wed, Jul 26, 2017 at 3:08 PM, jmh530 via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Wednesday, 26 July 2017 at 12:55:17 UTC, Mike Parker wrote:

 ---
 ~this() {}                    // Finalizer
 ~this()  nogc {}              // Finalizer
 ~this  deterministic {}       // Destructor
 ~this  nogc  deterministic {} // Destructor
 }
Yeah, this brings with it more flexibility. I'd prefer to avoid adding a new attribute for it, but this looks more interesting.
Some other options: ~~this() {} !this() {} !~this() {} this!(true) () {} //not really a big fan of this version
Jul 26 2017
prev sibling next sibling parent reply Guillaume Piolat <contact spam.com> writes:
On Wednesday, 26 July 2017 at 02:58:00 UTC, Mike Parker wrote:
 When cleaning up, the GC will ensure that all destructors are 
 run where they exist, followed by all finalizers. And destroy 
 would be changed to call rt_destruct instead of rt_finalize.

 Thoughts?
I don't get the distinction between destructors and "finalizers" but imho the problem is very much that the GC is calling ~this.
Jul 26 2017
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Wednesday, 26 July 2017 at 12:19:15 UTC, Guillaume Piolat 
wrote:
 I don't get the distinction between destructors and 
 "finalizers" but imho the problem is very much that the GC is 
 calling ~this.
Destructors are deterministic, finalizers are not. At least, that's how I understand the terms are commonly used.
Jul 26 2017
parent reply Guillaume Piolat <contact spam.com> writes:
On Wednesday, 26 July 2017 at 12:35:19 UTC, Mike Parker wrote:
 On Wednesday, 26 July 2017 at 12:19:15 UTC, Guillaume Piolat 
 wrote:
 I don't get the distinction between destructors and 
 "finalizers" but imho the problem is very much that the GC is 
 calling ~this.
Destructors are deterministic, finalizers are not. At least, that's how I understand the terms are commonly used.
Note that Andrei once proposed in 2014 that the GC wouldn't call destructors/finalizers at all: http://forum.dlang.org/post/ljrm0d$28vf$1 digitalmars.com
 We're considering deprecating ~this() for classes in the future.
Instead the forum community pushed back and what has been done is extending the calls to GC-allocated structs.
Jul 26 2017
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Wednesday, 26 July 2017 at 13:54:15 UTC, Guillaume Piolat 
wrote:
 On Wednesday, 26 July 2017 at 12:35:19 UTC, Mike Parker wrote:
 On Wednesday, 26 July 2017 at 12:19:15 UTC, Guillaume Piolat 
 wrote:
 I don't get the distinction between destructors and 
 "finalizers" but imho the problem is very much that the GC is 
 calling ~this.
Destructors are deterministic, finalizers are not. At least, that's how I understand the terms are commonly used.
Note that Andrei once proposed in 2014 that the GC wouldn't call destructors/finalizers at all: [...]
AFAICT that was mostly because it would have broken plenty of existing code. Properly separating destruction and finalization from each other with the current syntax remaining as finalizers and the new one for destructors would allow this to be done without major code breakage.
Jul 26 2017
parent reply Guillaume Piolat <contact spam.com> writes:
On Wednesday, 26 July 2017 at 14:10:19 UTC, Moritz Maxeiner wrote:
 AFAICT that was mostly because it would have broken plenty of 
 existing code.
The validity or purposefulness of such code is up to debate, a separate debate granted. Let's assume there is working code that such a change will break.
 Properly separating destruction and finalization from each 
 other with the current syntax remaining as finalizers and the 
 new one for destructors would allow this to be done without 
 major code breakage.
Sure, in the event D would like to transition towards a state where the GC doesn't call finalizers, it seems useful. From a marketing point of view having two destructors and keyword/syntax just for this would be hard to defend, and it would also need to explain the whole story. Personally I'd be for: ~this() { /* deterministic one */ } void finalize() { /* the one called by GC */ } But then no transition path. I'll defend the view point that there is _nothing_ useful to do in a finalizer except to check if the destructor has already been called.
Jul 26 2017
next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/26/17 10:57 AM, Guillaume Piolat wrote:

 I'll defend the view point that there is _nothing_ useful to do in a 
 finalizer except to check if the destructor has already been called.
For instance, a destructor could destroy recursively all members at that time. A finalizer would not be able to. The thing to do in a finalizer that is useful is to release any non-GC resources. -Steve
Jul 26 2017
parent reply Guillaume Piolat <contact spam.com> writes:
On Wednesday, 26 July 2017 at 15:15:03 UTC, Steven Schveighoffer 
wrote:
 On 7/26/17 10:57 AM, Guillaume Piolat wrote:

 I'll defend the view point that there is _nothing_ useful to 
 do in a finalizer except to check if the destructor has 
 already been called.
The thing to do in a finalizer that is useful is to release any non-GC resources. -Steve
Interesting case. Do we assume the finalizer is always called after the destructor? - If yes, then releasing these non-GC resources could have been possible in the destructor too. The only lost generality would be if releasing such non-GC resources would be faster from the GC thread (could well be since pooled). - else, it's a case of the finalizer being calld by the GC and the destructor not being called. Is this considered a bug? | | - If yes, then point of releasing resources is moot since we have a bug. | | - If not, it means we want to allow not calling destructors. | | | | => this implies we think finalizers will be called | | I'll make claim this works for process-wide resources somehow (we stopped the last debate here), but not transient ones (eg: mutex) because of false pointers. The finalizer might be released late. From these premises I conclude that the instructions given to new D programmers would be: 1. you should destroy resources deterministically 2. however GC objects owning resources may release them in their finalizer * except if you can't release them from any thread * except if these resources should be released before the GC shutdown (and then you have to explain why finalizer might not be called right now).
Jul 26 2017
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/26/17 12:01 PM, Guillaume Piolat wrote:
 On Wednesday, 26 July 2017 at 15:15:03 UTC, Steven Schveighoffer wrote:
 On 7/26/17 10:57 AM, Guillaume Piolat wrote:

 I'll defend the view point that there is _nothing_ useful to do in a 
 finalizer except to check if the destructor has already been called.
The thing to do in a finalizer that is useful is to release any non-GC resources.
Interesting case. Do we assume the finalizer is always called after the destructor?
Yes, finalizer and destructor should be called on deterministic destruction (no need to repeat the finalizer code in the destructor).
 - If yes, then releasing these non-GC resources could have been possible 
 in the destructor too. The only lost generality would be if releasing 
 such non-GC resources would be faster from the GC thread (could well be 
 since pooled).
But the destructor can't be called from the GC. In cases where the GC is being used to clean up the object (whether on purpose or by accident), then if you didn't have a finalizer, the resource leaks.
 - else, it's a case of the finalizer being calld by the GC and the 
 destructor not being called. Is this considered a bug?
No.
 |
 |   - If yes, then point of releasing resources is moot since we have a 
 bug.
 |
 |   - If not, it means we want to allow not calling destructors.
Of course, the idea is that a destructor does what the GC would have done (recursively call the finalizers of members, and optionally clean up memory if wholly owned).
 |   |
 |   |   => this implies we think finalizers will be called
 |   |      I'll make claim this works for process-wide resources somehow 
 (we stopped the last debate here), but not transient ones (eg: mutex) 
 because of false pointers. The finalizer might be released late.
 
  From these premises I conclude that the instructions given to new D 
 programmers would be:
 
    1. you should destroy resources deterministically
 
    2. however GC objects owning resources may release them in their 
 finalizer
      * except if you can't release them from any thread
      * except if these resources should be released before the GC 
 shutdown (and then you have to explain why finalizer might not be called 
 right now).
Today, the finalizer is essentially a last-effort to clean up resources that would otherwise leak. The reason deterministic destruction sucks today is because once you go 2 levels deep, the only tool available (~this) cannot release any resources (because it's not legal to access GC-allocated members inside ~this). Adding a destructor concept would fix this issue, and make deterministic destruction more pleasant. Look at any library that contains such non-memory resources. These things inevitably implement some sort of "close()" or "release()" function, which does the deterministic destruction. It's just a destructor with a different name. -Steve
Jul 26 2017
parent reply Guillaume Piolat <first.last gmail.com> writes:
On Wednesday, 26 July 2017 at 22:29:50 UTC, Steven Schveighoffer 
wrote:
 Today, the finalizer is essentially a last-effort to clean up 
 resources that would otherwise leak.
My point is that this "last chance" to clean up resources should only be used to tell deterministic destruction wasn't acheived (for reasons given in my former post: it's _too hard to explain_ how to do resource management else).
 The reason deterministic destruction sucks today is because 
 once you go 2 levels deep, the only tool available (~this) 
 cannot release any resources (because it's not legal to access 
 GC-allocated members inside ~this).
You already know it, but if you commit to not doing anything in the finalizer case, then ~this doesn't suck, it works as a destructor and the restrictions lift. So "suck" is a bit strong.
 Look at any library that contains such non-memory resources. 
 These things inevitably implement some sort of "close()" or 
 "release()" function, which does the deterministic destruction. 
 It's just a destructor with a different name.
You could also merge the close() function with ~this() and remove ~this()-called-as-finalizer, which is the position I defend. If I count correctly there are 3 restrictions on the type of resource that can be GC-finalized: - can be destroyed from any thread - do not depend on another resource released by GC finalization, for it's own release https://forum.dlang.org/post/pmulowxpikjjffkrscct forum.dlang.org - can have a delayed finalization That's the reason why I thnk deterministic destruction for non-memory resources is easier than more mixed strategies.
Jul 27 2017
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/27/17 4:23 AM, Guillaume Piolat wrote:
 On Wednesday, 26 July 2017 at 22:29:50 UTC, Steven Schveighoffer wrote:
 Today, the finalizer is essentially a last-effort to clean up 
 resources that would otherwise leak.
My point is that this "last chance" to clean up resources should only be used to tell deterministic destruction wasn't acheived (for reasons given in my former post: it's _too hard to explain_ how to do resource management else).
Consider reference counting with cycles. The proposal from Walter/Andrei is to do reference counting to clean up everything but cycles. For cycles, the GC will take care of it. So how do you do this correctly without having two separate mechanisms?
 The reason deterministic destruction sucks today is because once you 
 go 2 levels deep, the only tool available (~this) cannot release any 
 resources (because it's not legal to access GC-allocated members 
 inside ~this).
You already know it, but if you commit to not doing anything in the finalizer case, then ~this doesn't suck, it works as a destructor and the restrictions lift. So "suck" is a bit strong.
This is an unworkable solution. One simple time you forget to clean up deterministically and then you corrupt memory by using members that are already cleaned up. Or, you disable calling destructors in the GC, and instead leak resources. Or I suppose you could crash your application. This would be a fun bug to figure out... -Steve
Jul 27 2017
next sibling parent Dgame <r.schuett.1987 gmail.com> writes:
 Consider reference counting with cycles. The proposal from 
 Walter/Andrei is to do reference counting to clean up 
 everything but cycles. For cycles, the GC will take care of it.
That is the PHP way to do things. :) It's neat but I still think the only real thing to deal with resources is built-in ownership like Rust does it. But that's a bit out of question for D2 I suppose. What's the state of the RC approach?
Jul 27 2017
prev sibling parent Guillaume Piolat <contact spam.com> writes:
For cycle in RC you either do it like other RC system and break 
cycles manually, or create a parent owner to own every things 
pointing to each other and having cycles.

On Thursday, 27 July 2017 at 11:43:37 UTC, Steven Schveighoffer 
wrote:
 This is an unworkable solution.
Not at all unworkable, it's much easier than mixed strategies.
 One simple time you forget to clean up deterministically and 
 then you corrupt memory by using members that are already 
 cleaned up.
Once again, this is the reason of the existence of the GC-Proof-resource-class, which have the GC warn you of non-deterministic destruction at debug time. In the very case you mention it will tell you "you have forgot to release one resource T deterministically".
 Or, you disable calling destructors in the GC, and instead leak 
 resources.
As I said in previous messages, not all resources can be destroyed by the GC: they have to fit into 3 different constraints. I'll leave the discussion, you seem to ignore my arguments in what seems as an attempt to have the last word.
Jul 27 2017
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 26 July 2017 at 14:57:14 UTC, Guillaume Piolat 
wrote:
 But then no transition path.
Does seem a bit like a nightmare... It may facilitate a transition to add a destructor member better?). Then have a deprecation for ~this and tell people to use finalize or destructor, and then add back ~this with the property that the default ~this calls destructor if that's there.
Jul 26 2017
parent Guillaume Piolat <contact spam.com> writes:
On Wednesday, 26 July 2017 at 15:42:45 UTC, jmh530 wrote:
 On Wednesday, 26 July 2017 at 14:57:14 UTC, Guillaume Piolat 
 wrote:
 But then no transition path.
Does seem a bit like a nightmare... It may facilitate a transition to add a destructor member better?). Then have a deprecation for ~this and tell people to use finalize or destructor, and then add back ~this with the property that the default ~this calls destructor if that's there.
Well I like this idea best. To split C in A and B, deprecate C and tell what to do instead.
Jul 26 2017
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/26/17 8:19 AM, Guillaume Piolat wrote:
 On Wednesday, 26 July 2017 at 02:58:00 UTC, Mike Parker wrote:
 When cleaning up, the GC will ensure that all destructors are run 
 where they exist, followed by all finalizers. And destroy would be 
 changed to call rt_destruct instead of rt_finalize.

 Thoughts?
I don't get the distinction between destructors and "finalizers" but imho the problem is very much that the GC is calling ~this.
In D, the difference is when the thing is called. If it's called deterministically, either by function or by the compiler, then you have a lot more flexibility. If it's by the GC, it can be out of order with regards to your members, it could be on a different thread than the one that owned it, etc. Regarding the OP, I think we really should strive to have something to fix this issue. A poor-mans distinction could be done by checking whether the GC is currently the one destroying (a flag is available, but isn't publicly accessible), though that could get expensive. -Steve
Jul 26 2017
parent reply Mike Parker <aldacron gmail.com> writes:
On Wednesday, 26 July 2017 at 12:43:27 UTC, Steven Schveighoffer 
wrote:
 Regarding the OP, I think we really should strive to have 
 something to fix this issue.

 A poor-mans distinction could be done by checking whether the 
 GC is currently the one destroying (a flag is available, but 
 isn't publicly accessible), though that could get expensive.
That's essentially what Guillaume's "GC-proof resource class" idiom does now. https://p0nce.github.io/d-idioms/#GC-proof-resource-class
Jul 26 2017
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/26/17 8:57 AM, Mike Parker wrote:
 On Wednesday, 26 July 2017 at 12:43:27 UTC, Steven Schveighoffer wrote:
 Regarding the OP, I think we really should strive to have something to 
 fix this issue.

 A poor-mans distinction could be done by checking whether the GC is 
 currently the one destroying (a flag is available, but isn't publicly 
 accessible), though that could get expensive.
That's essentially what Guillaume's "GC-proof resource class" idiom does now. https://p0nce.github.io/d-idioms/#GC-proof-resource-class
Yeah, I've seen that. https://issues.dlang.org/show_bug.cgi?id=17563 -Steve
Jul 26 2017
prev sibling parent reply Dgame <r.schuett.1987 gmail.com> writes:
I don't get it. The GC collects the objects which aren't in use 
anymore. The order in which this is currently happening is not 
specified. So, how are the destructors supposed to be called in 
the right order? Manually? ARC? As far as I understand it, the GC 
can't do it, otherwise we wouldn't have the "random" order in 
which the finalizer are called in the first place.
Jul 26 2017
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Wednesday, 26 July 2017 at 17:38:28 UTC, Dgame wrote:
 I don't get it. The GC collects the objects which aren't in use 
 anymore. The order in which this is currently happening is not 
 specified. So, how are the destructors supposed to be called in 
 the right order? Manually? ARC?
After the split: Destructors are only for deterministic end of object lifetime, so yes, they are to be called by any scheme (such as manual management via destroy and reference counting - which is likely also implemented as calling destroy) that is deterministic. Finalizers are for nondeterministic schemes such as the GC. The GC *never* calls destructors directly, only finalizers. A finalizer might manually call a destructor, but a destructor may never call a finalizer.
Jul 26 2017
next sibling parent reply Dgame <r.schuett.1987 gmail.com> writes:
On Wednesday, 26 July 2017 at 18:33:58 UTC, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 17:38:28 UTC, Dgame wrote:
 I don't get it. The GC collects the objects which aren't in 
 use anymore. The order in which this is currently happening is 
 not specified. So, how are the destructors supposed to be 
 called in the right order? Manually? ARC?
After the split: Destructors are only for deterministic end of object lifetime, so yes, they are to be called by any scheme (such as manual management via destroy and reference counting - which is likely also implemented as calling destroy) that is deterministic. Finalizers are for nondeterministic schemes such as the GC. The GC *never* calls destructors directly, only finalizers. A finalizer might manually call a destructor, but a destructor may never call a finalizer.
Alright, thanks for the clarification. I've briefly hoped for some sort of miracle such as deterministic object lifetime without manual interaction. :)
Jul 26 2017
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Wednesday, 26 July 2017 at 19:18:48 UTC, Dgame wrote:
 Alright, thanks for the clarification. I've briefly hoped for 
 some sort of miracle such as deterministic object lifetime 
 without manual interaction. :)
I'm not sure what scheme you are trying to describe here, could you give a code example of what you hoped for?
Jul 26 2017
parent Dgame <r.schuett.1987 gmail.com> writes:
On Wednesday, 26 July 2017 at 20:02:02 UTC, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 19:18:48 UTC, Dgame wrote:
 Alright, thanks for the clarification. I've briefly hoped for 
 some sort of miracle such as deterministic object lifetime 
 without manual interaction. :)
I'm not sure what scheme you are trying to describe here, could you give a code example of what you hoped for?
Built-in Ownership/RC for objects.
Jul 26 2017
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/26/17 2:33 PM, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 17:38:28 UTC, Dgame wrote:
 I don't get it. The GC collects the objects which aren't in use 
 anymore. The order in which this is currently happening is not 
 specified. So, how are the destructors supposed to be called in the 
 right order? Manually? ARC?
After the split: Destructors are only for deterministic end of object lifetime, so yes, they are to be called by any scheme (such as manual management via destroy and reference counting - which is likely also implemented as calling destroy) that is deterministic. Finalizers are for nondeterministic schemes such as the GC. The GC *never* calls destructors directly, only finalizers. A finalizer might manually call a destructor, but a destructor may never call a finalizer.
Actually, it's the opposite. A finalizer can never call anything on its members because it doesn't know if it's being destroyed by the GC. The destructor is ensured that the entire structure is intact, so it can do whatever it wants. -Steve
Jul 26 2017
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Wednesday, 26 July 2017 at 22:33:23 UTC, Steven Schveighoffer 
wrote:
 On 7/26/17 2:33 PM, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 17:38:28 UTC, Dgame wrote:
 I don't get it. The GC collects the objects which aren't in 
 use anymore. The order in which this is currently happening 
 is not specified. So, how are the destructors supposed to be 
 called in the right order? Manually? ARC?
After the split: Destructors are only for deterministic end of object lifetime, so yes, they are to be called by any scheme (such as manual management via destroy and reference counting - which is likely also implemented as calling destroy) that is deterministic. Finalizers are for nondeterministic schemes such as the GC. The GC *never* calls destructors directly, only finalizers. A finalizer might manually call a destructor, but a destructor may never call a finalizer.
Actually, it's the opposite.
It's a matter of definition and this is the behaviour I would define, because
 A finalizer can never call anything on its members because it 
 doesn't know if it's being destroyed by the GC.
This falsely assumes that all members point into the GC pool. A finalizer may freely work on non-pointer members and pointer members that target objects outside the GC pool which the programmer knows to be valid at finalization (e.g. they are manually managed). Whether or not it makes sense for the finalizer to call the destructor is something the programmer has to decide on a per use case basis.
 The destructor is ensured that the entire structure is intact, 
 so it can do whatever it wants.
The point is that I saw (and see) no reason for a destructor to ever call a finalizer.
Jul 26 2017
next sibling parent Moritz Maxeiner <moritz ucworks.org> writes:
On Wednesday, 26 July 2017 at 23:28:38 UTC, Moritz Maxeiner wrote:
 This falsely assumes that all members point into the GC pool. A 
 finalizer may freely work on non-pointer members and pointer 
 members that target objects outside the GC pool which the 
 programmer knows to be valid at finalization (e.g. they are 
 manually managed).
* freely with the exception of not allocating using the GC, of course.
Jul 26 2017
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/26/17 7:28 PM, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 22:33:23 UTC, Steven Schveighoffer wrote:
 On 7/26/17 2:33 PM, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 17:38:28 UTC, Dgame wrote:
 I don't get it. The GC collects the objects which aren't in use 
 anymore. The order in which this is currently happening is not 
 specified. So, how are the destructors supposed to be called in the 
 right order? Manually? ARC?
After the split: Destructors are only for deterministic end of object lifetime, so yes, they are to be called by any scheme (such as manual management via destroy and reference counting - which is likely also implemented as calling destroy) that is deterministic. Finalizers are for nondeterministic schemes such as the GC. The GC *never* calls destructors directly, only finalizers. A finalizer might manually call a destructor, but a destructor may never call a finalizer.
Actually, it's the opposite.
It's a matter of definition and this is the behaviour I would define, because
 A finalizer can never call anything on its members because it doesn't 
 know if it's being destroyed by the GC.
This falsely assumes that all members point into the GC pool.
Yes, I should have qualified GC members.
 A 
 finalizer may freely work on non-pointer members and pointer members 
 that target objects outside the GC pool which the programmer knows to be 
 valid at finalization (e.g. they are manually managed).
 Whether or not it makes sense for the finalizer to call the destructor 
 is something the programmer has to decide on a per use case basis.
No, because a destructor can safely assume it can look at GC members' data. So a finalizer can never call it.
 The destructor is ensured that the entire structure is intact, so it 
 can do whatever it wants.
The point is that I saw (and see) no reason for a destructor to ever call a finalizer.
An example: class File { int fd; ubyte[] buffer; // avoiding bikeshed issues by using clear names destructor() { finalizer(); delete buffer; } finalizer() { close(fd); } } No reason to repeat the finalizer code in the destructor. -Steve
Jul 26 2017
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Thursday, 27 July 2017 at 00:00:08 UTC, Steven Schveighoffer 
wrote:
 On 7/26/17 7:28 PM, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 22:33:23 UTC, Steven 
 Schveighoffer wrote:
 On 7/26/17 2:33 PM, Moritz Maxeiner wrote:
 A finalizer may freely work on non-pointer members and pointer 
 members that target objects outside the GC pool which the 
 programmer knows to be valid at finalization (e.g. they are 
 manually managed).
 Whether or not it makes sense for the finalizer to call the 
 destructor is something the programmer has to decide on a per 
 use case basis.
No, because a destructor can safely assume it can look at GC members' data. So a finalizer can never call it.
No, whether a finalizer can safely call a destructor depends on the destructor's body. If the destructor doesn't access GC members and doesn't allocate using the GC, the finalizer can safely call it. That's why it's the programmer's job to determine this on a use case basis.
 The destructor is ensured that the entire structure is 
 intact, so it can do whatever it wants.
The point is that I saw (and see) no reason for a destructor to ever call a finalizer.
An example: class File { int fd; ubyte[] buffer; // avoiding bikeshed issues by using clear names destructor() { finalizer(); delete buffer; } finalizer() { close(fd); } } No reason to repeat the finalizer code in the destructor.
I concede the point.
Jul 26 2017
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/26/17 8:20 PM, Moritz Maxeiner wrote:
 On Thursday, 27 July 2017 at 00:00:08 UTC, Steven Schveighoffer wrote:
 On 7/26/17 7:28 PM, Moritz Maxeiner wrote:
 On Wednesday, 26 July 2017 at 22:33:23 UTC, Steven Schveighoffer wrote:
 On 7/26/17 2:33 PM, Moritz Maxeiner wrote:
 A finalizer may freely work on non-pointer members and pointer 
 members that target objects outside the GC pool which the programmer 
 knows to be valid at finalization (e.g. they are manually managed).
 Whether or not it makes sense for the finalizer to call the 
 destructor is something the programmer has to decide on a per use 
 case basis.
No, because a destructor can safely assume it can look at GC members' data. So a finalizer can never call it.
No, whether a finalizer can safely call a destructor depends on the destructor's body. If the destructor doesn't access GC members and doesn't allocate using the GC, the finalizer can safely call it. That's why it's the programmer's job to determine this on a use case basis.
I suppose this is true, but in practice, you wouldn't in a destructor that which is available for a finalizer. In other words, you simply wouldn't implement such a destructor. It's also very fragile as the maintainer of the destructor is working under different assumptions from the finalizer. If you call the destructor from the finalizer, then the rules of the finalizer infect the destructor. -Steve
Jul 26 2017
parent Swoorup Joshi <swoorupjoshi gmail.com> writes:
I might be hated for saying this but I still think the GC problem 
should not be apart of D language. Make it the user's or third 
party problem.
Jul 27 2017