August 29, 2015
On Saturday, 29 August 2015 at 14:17:10 UTC, rsw0x wrote:
>
> All of this could be fixed by not letting the GC call destructors. It's a bad, error-prone design to begin with and I guarantee any semi-large D program is probably abusing undefined behavior due to it.

Indeed.
August 29, 2015
On Saturday, 29 August 2015 at 14:32:27 UTC, Timon Gehr wrote:
> But then classes with destructors shouldn't be allowed to be allocated on the GC heap in the first place, which is a PITA as well. (Note that classes/arrays can have destructors because some component structs have them; structs generally assume that their destructors will be called.)

I don't quite follow the reasonning here. If GC doesn't call the destructor then this same destructor is no more than a normal method (with restrictions... would those still stand?) that is the default destruction method to be called by things like scoped!T or destroy if explicit destruction is needed.

I think there should be a separation of concerns that isn't possible right now. Freeing ressources and freeing memory isn't the same thing and they should be decoupled. I think a destructor is there to free ressources, and the GC is there to free memory. If the GC doesn't call the destructor then why should having a destructor have anything to do with the GC?

Or do you fear for classes whose memory would be freed before freeing its ressources? That could be a problem... In that case I think the best option would be to allow them to be allocated by the GC but GC-ing it if the destructor hasn't been called should spawn an error (or something like that, haven't taken the time to think through it). Or maybe it shouldn't be marked as garbage if the destructor hasn't been called.

I think of it as a simple switch hasDestructorBeenCalled  that would be set to true if no destructor exists or if it has been called, and false otherwise, and would prevent GC-ing (or crash... I don't know what's best) of the object if false.

That way simple classes stay simple, complex classes can live on the heap happily without fearing collection while being able to reliably free ressources.
August 29, 2015
On Saturday, 29 August 2015 at 13:14:26 UTC, ponce wrote:
> class MyResource
> {
>     void* handle;
>
>     this()
>     {
>         handle = create_handle();
>     }
>
>     ~this()
>     {
>         if (handle != null) // must support repeated calls for the case (called by .destroy + called by GC later)
>         {
>             ensureNotInGC("MyResource");
>             free_handle(handle);
>         }
>     }
> }

I don't think it is a good idea to call create_handle() in the constructor. Why not just pass a handle into the Resource?
August 29, 2015
On 08/29/2015 05:20 PM, cym13 wrote:
> On Saturday, 29 August 2015 at 14:32:27 UTC, Timon Gehr wrote:
>> But then classes with destructors shouldn't be allowed to be allocated
>> on the GC heap in the first place, which is a PITA as well. (Note that
>> classes/arrays can have destructors because some component structs
>> have them; structs generally assume that their destructors will be
>> called.)
>
> I don't quite follow the reasonning here. If GC doesn't call the
> destructor then this same destructor is no more than a normal method

If it is no more than a normal method:
- Why have special syntax for it?
- Why should Object have it?

> (with restrictions... would those still stand?) that is the default
> destruction method to be called by things like scoped!T or destroy if
> explicit destruction is needed.
> ...

If there is a destructor, this (usually) means that explicit destruction is needed.

Again, note that if I have

import std.collection: Array;
class C{ Array arr; ... }

then now C implicitly has a destructor (that does nothing but call arr's destructor which may in turn free memory on the C heap).

Constructor and destructor are supposed to frame the lifetime of an instance. Destructors are in the language so that the language can help with enforcing this. If there's a built-in and expected way to violate this property, the syntactic similarity of constructors and destructors is misleading, and the features are less useful.

> I think there should be a separation of concerns that isn't possible
> right now. Freeing ressources and freeing memory isn't the same thing
> and they should be decoupled.

Memory is a resource, and not all memory is allocated by the GC. (c.f. http://erdani.com/d/phobos-prerelease/std_experimental_allocator.html)

> I think a destructor is there to free
> ressources, and the GC is there to free memory. If the GC doesn't call
> the destructor then why should having a destructor have anything to do
> with the GC?
>
> Or do you fear for classes whose memory would be freed before freeing
> its ressources?

For example. The general idea is that there is no point in having language features to deal with complex issues if they actually don't.

> That could be a problem...  In that case I think the best
> option would be to allow them to be allocated by the GC

I assume this means allow 'new Class()' even if Class has a destructor.

> but GC-ing it if
> the destructor hasn't been called should spawn an error (or something
> like that, haven't taken the time to think through it).

Why is it sensible to have the same syntax for allocation if
deallocation/destruction needs to be handled differently?

> Or maybe it shouldn't be marked as garbage if the destructor hasn't been called.
> ...

I.e. leak memory.

> I think of it as a simple switch hasDestructorBeenCalled  that would be
> set to true if no destructor exists or if it has been called, and false
> otherwise, and would prevent GC-ing (or crash... I don't know what's
> best) of the object if false.
>
> That way simple classes stay simple,

Simple classes get an additional hidden field. Even the monitor field is too much.

> complex classes can live on the
> heap happily without fearing collection while being able to reliably
> free ressources.

This does not make a lot of sense. If there is no live reference to a class managing a resource and it would then need to "fear" collection, this means that the resource has been leaked.
August 29, 2015
On 08/29/2015 04:45 PM, rsw0x wrote:
> On Saturday, 29 August 2015 at 14:32:27 UTC, Timon Gehr wrote:
>> On 08/29/2015 04:20 PM, cym13 wrote:
>>> On Saturday, 29 August 2015 at 14:17:10 UTC, rsw0x wrote:
>>>> On Saturday, 29 August 2015 at 13:14:26 UTC, ponce wrote:
>>>>> ...
>>>>
>>>> All of this could be fixed by not letting the GC call destructors.
>>>> It's a bad, error-prone design to begin with and I guarantee any
>>>> semi-large D program is probably abusing undefined behavior due to it.
>>>
>>> After reading all that, I too am convinced that the GC shouldn't call
>>> the destructor.
>>
>> But then classes with destructors shouldn't be allowed to be allocated
>> on the GC heap in the first place, which is a PITA as well. (Note that
>> classes/arrays can have destructors because some component structs
>> have them; structs generally assume that their destructors will be
>> called.)
>
> make classes with destructors(and structs allocated via new) have RC
> semantics.

RC is an especially eager form of GC, that does not deal with cycles by default. Why does it help?
August 30, 2015
On Saturday, 29 August 2015 at 22:50:44 UTC, Timon Gehr wrote:
> If it is no more than a normal method:
> - Why have special syntax for it?
> - Why should Object have it?

I see it a bit like popFront() and other range methods that are normal methods but take part in common process so their name was standardized and they are usually used through a special syntax. I have no answer for the Object comment, I don't see why it should have it if it doesn't do anything.

> If there is a destructor, this (usually) means that explicit destruction is needed.
>
> Again, note that if I have
>
> import std.collection: Array;
> class C{ Array arr; ... }
>
> then now C implicitly has a destructor (that does nothing but call arr's destructor which may in turn free memory on the C heap).
>
> Constructor and destructor are supposed to frame the lifetime of an instance. Destructors are in the language so that the language can help with enforcing this. If there's a built-in and expected way to violate this property, the syntactic similarity of constructors and destructors is misleading, and the features are less useful.

> Memory is a resource, and not all memory is allocated by the GC. (c.f. http://erdani.com/d/phobos-prerelease/std_experimental_allocator.html)

It isn't quite the same kind of resource as, say, a File handle that would cause more problem if not handed closed in time. Letting the GC collect when it estimates that memory is low is often more than good enough. Also, whoever allocates/opens a resource is the one that should be freeing/closing it. That goes for memory as well.

> I assume this means allow 'new Class()' even if Class has a destructor.

It means allow this class to be managed by the GC


> Why is it sensible to have the same syntax for allocation if
> deallocation/destruction needs to be handled differently?

Because memory management is the same here, internal resource management isn't

> I.e. leak memory.

or being incorrectly GC-ed, that seems more dangerous to me... Well it shouldn't happen anyway so crashing should actually be ok.

> Simple classes get an additional hidden field. Even the monitor field is too much.

They're using the GC and you're worrying about 1 bit of overhead? Ok, not having it would be better but still...

> This does not make a lot of sense. If there is no live reference to a class managing a resource and it would then need to "fear" collection, this means that the resource has been leaked.
Yeah, after thinking about it I agree with that.

---

Reading this made me think about why I'd want all classes to be managed by the GC. I think what I fear most is inconsistence in usage. Also, I always feel uneasy from this exodus away from the GC. Having a GC is great, I don't want to be forced to always manage memory by hand. Not being able to use a class (or struct, because I think it would apply too) because it has a destructor would be a disruptive hard to explain change.

The thing is I still think all resources aren't equal and having a mixed style when you do explicitely only what's necessary could prove powerful in D. I need to mature this idea though.

Letting that aside, I can't find a good reason for thoses objects to be used by the GC if their memory must be explicitely freed, I now think you're right about that. The more I think about it the more that solution makes sense to me... But it would be one hell of a breaking change so it probably won't happen... too bad because it is a better idea than not calling the destructor. Not calling it would produce leaks, calling ponce's leak detector would produce runtime errors... In that case having compile-time errors sounds better. And if one need it to fit in the garbage collector anyway but wants to free some resource he sure can afford a .close() method that would make it explicit instead of providing a destructor.

Yeah, that sounds right as it is.

August 30, 2015
On Saturday, 29 August 2015 at 16:12:52 UTC, skoppe wrote:
>
> I don't think it is a good idea to call create_handle() in the constructor. Why not just pass a handle into the Resource?

This isn't related to the topic.
August 30, 2015
On Saturday, 29 August 2015 at 23:08:45 UTC, Timon Gehr wrote:
> On 08/29/2015 04:45 PM, rsw0x wrote:
>> On Saturday, 29 August 2015 at 14:32:27 UTC, Timon Gehr wrote:
>>> On 08/29/2015 04:20 PM, cym13 wrote:
>>>> On Saturday, 29 August 2015 at 14:17:10 UTC, rsw0x wrote:
>>>>> [...]
>>>>
>>>> After reading all that, I too am convinced that the GC shouldn't call
>>>> the destructor.
>>>
>>> But then classes with destructors shouldn't be allowed to be allocated
>>> on the GC heap in the first place, which is a PITA as well. (Note that
>>> classes/arrays can have destructors because some component structs
>>> have them; structs generally assume that their destructors will be
>>> called.)
>>
>> make classes with destructors(and structs allocated via new) have RC
>> semantics.
>
> RC is an especially eager form of GC, that does not deal with cycles by default. Why does it help?

The problem is that there's no guarantee the destructor will run with the GC, no guarantee what thread it will run on, and no guarantee on when it will run. RC guarantees all three of these outside of cycles.
August 30, 2015
On Sunday, 30 August 2015 at 09:54:31 UTC, ponce wrote:
> On Saturday, 29 August 2015 at 16:12:52 UTC, skoppe wrote:
>>
>> I don't think it is a good idea to call create_handle() in the constructor. Why not just pass a handle into the Resource?
>
> This isn't related to the topic.

By putting create_handle in the constructor, you inevitably end up putting free_handle in some (destructor) function. The problems you are having might be different/easier when you make something else do the (de)construction. Like, say, a ResourceManager.
August 30, 2015
On Sunday, 30 August 2015 at 11:45:36 UTC, skoppe wrote:
> On Sunday, 30 August 2015 at 09:54:31 UTC, ponce wrote:
>> On Saturday, 29 August 2015 at 16:12:52 UTC, skoppe wrote:
>>>
>>> I don't think it is a good idea to call create_handle() in the constructor. Why not just pass a handle into the Resource?
>>
>> This isn't related to the topic.
>
> By putting create_handle in the constructor, you inevitably end up putting free_handle in some (destructor) function. The problems you are having might be different/easier when you make something else do the (de)construction. Like, say, a ResourceManager.

The handle is not important, the idiom applies equally to any class with a non-trivial destructor.