July 26, 2017
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. :)
July 26, 2017
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?
July 26, 2017
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.
July 26, 2017
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
July 26, 2017
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

July 26, 2017
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.

July 26, 2017
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.
July 26, 2017
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
July 27, 2017
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.
July 26, 2017
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