Thread overview
Calling destroy on struct pointer
Feb 25, 2017
Radu
Feb 25, 2017
Ali Çehreli
Feb 25, 2017
Radu
Feb 25, 2017
Moritz Maxeiner
Feb 25, 2017
Moritz Maxeiner
Feb 25, 2017
Radu
Feb 25, 2017
Moritz Maxeiner
Feb 25, 2017
Radu
Feb 25, 2017
Moritz Maxeiner
Feb 28, 2017
Radu
February 25, 2017
I'm puzzled by the way destroy works when passed a pointer to a struct, observe:
----------------------code.d----------------------
int i;

struct C
{
	this(ref int i)
	{
		++i;
		ii = &i;
	}
	
	~this()
	{
		--(*ii);
	}
	int* ii;
}
unittest
{
    C c = C(i);
    C* cc = &c;
    destroy(cc);
    assert(i == 0); // dtor not called
    destroy(*cc);
    assert(i == 0); // dtor called
}
int main()
{
	
	return 0;
}
----------------------code.d----------------------
destroy(cc) -> does c = C.init
destroy(*cc); -> calls the C dtor

Is this by design? If so - how can I destroy and get the dtor called without dereferencing the pointer?
Currently I resort to calling delete on the pointer so dtor gets called.

February 25, 2017
On 02/25/2017 12:17 AM, Radu wrote:

> destroy(cc) -> does c = C.init
> destroy(*cc); -> calls the C dtor
>
> Is this by design? If so - how can I destroy and get the dtor called
> without dereferencing the pointer?

It's by design because setting a pointer to null can be considered as destroying the pointer. Dereferencing is the right way of destroying the object through the pointer.

I had added the following warning after somebody else was burnt by this feature. :)

  http://ddili.org/ders/d.en/memory.html#ix_memory.destroy

Ali

February 25, 2017
On Saturday, 25 February 2017 at 08:36:02 UTC, Ali Çehreli wrote:
> On 02/25/2017 12:17 AM, Radu wrote:
>
> > destroy(cc) -> does c = C.init
> > destroy(*cc); -> calls the C dtor
> >
> > Is this by design? If so - how can I destroy and get the dtor
> called
> > without dereferencing the pointer?
>
> It's by design because setting a pointer to null can be considered as destroying the pointer. Dereferencing is the right way of destroying the object through the pointer.
>
> I had added the following warning after somebody else was burnt by this feature. :)
>
>   http://ddili.org/ders/d.en/memory.html#ix_memory.destroy
>
> Ali

I think this is BAD. Why?

- it is one of those WAT?? moments that brings a RTFM slap to you. The defaults should not be surprising, and in this case straight dangerous as it can lead to leaks.
- it is not always possible to dereference the pointer, think some circular structures where deref would get you one of those fwd. declaration errors.
- the deprecated delete will call the dtor, destroy is suppose to replace delete - hence it should work the same.

In my opinion destroy should do this:
- call dtor if the pointer type has one defined
- nullify the pointer

This is what I was expecting anyhow to happen...

February 25, 2017
On Saturday, 25 February 2017 at 10:44:07 UTC, Radu wrote:
> On Saturday, 25 February 2017 at 08:36:02 UTC, Ali Çehreli wrote:
>> On 02/25/2017 12:17 AM, Radu wrote:
>>
>> > destroy(cc) -> does c = C.init
>> > destroy(*cc); -> calls the C dtor
>> >
>> > Is this by design? If so - how can I destroy and get the dtor
>> called
>> > without dereferencing the pointer?
>>
>> It's by design because setting a pointer to null can be considered as destroying the pointer. Dereferencing is the right way of destroying the object through the pointer.
>>
>> I had added the following warning after somebody else was burnt by this feature. :)
>>
>>   http://ddili.org/ders/d.en/memory.html#ix_memory.destroy
>>
>> Ali
>
> I think this is BAD. Why?
>
> - it is one of those WAT?? moments that brings a RTFM slap to you. The defaults should not be surprising, and in this case straight dangerous as it can lead to leaks.

Unfortunately, I don't think it's viable to change destroy (see below), it would probably be better to cover this in the dlang tour (if it isn't already).

> - it is not always possible to dereference the pointer, think some circular structures where deref would get you one of those fwd. declaration errors.

In the interest of learning, could you provide an example of such a case?

> - the deprecated delete will call the dtor, destroy is suppose to replace delete - hence it should work the same.

AFAIK destroy isn't supposed to replace delete, since delete is destruction+deallocation and destroy is only destruction; and by that definition they cannot work the same: AFAIR multiple deletes are illegal (since that equals use after free), whereas destroy can be used on the same object as often as you want (the destructor will only be called the first time).

>
> In my opinion destroy should do this:
> - call dtor if the pointer type has one defined
> - nullify the pointer
>
> This is what I was expecting anyhow to happen...

This change would be backwards-incompatible and breaks user code, especially manual memory management:

---
struct A {}

auto a = cast (A*) malloc(A.sizeof); // Allocate
emplace(a, 42);                      // Construct

destroy(a);                          // Destruct
free(a);                             // Deallocate
---

if destroy were to already nullify a, how were one supposed to deallocate a?
February 25, 2017
On Saturday, 25 February 2017 at 13:14:24 UTC, Moritz Maxeiner wrote:
> ---
> struct A {}
>
> auto a = cast (A*) malloc(A.sizeof); // Allocate
> emplace(a, 42);                      // Construct
>
> destroy(a);                          // Destruct
> free(a);                             // Deallocate
> ---

Sorry for double posting, I failed at copy-paste, here's the correct example:

---
struct A { int i; }

auto a = cast (A*) malloc(A.sizeof); // Allocate
emplace(a, 42);                      // Construct

destroy(a);                          // Destruct
free(a);                             // Deallocate
---
February 25, 2017
On Saturday, 25 February 2017 at 13:14:24 UTC, Moritz Maxeiner wrote:
> On Saturday, 25 February 2017 at 10:44:07 UTC, Radu wrote:
>> On Saturday, 25 February 2017 at 08:36:02 UTC, Ali Çehreli wrote:
>>> On 02/25/2017 12:17 AM, Radu wrote:
>>>
>>> > destroy(cc) -> does c = C.init
>>> > destroy(*cc); -> calls the C dtor
>>> >
>>> > Is this by design? If so - how can I destroy and get the dtor
>>> called
>>> > without dereferencing the pointer?
>>>
>>> It's by design because setting a pointer to null can be considered as destroying the pointer. Dereferencing is the right way of destroying the object through the pointer.
>>>
>>> I had added the following warning after somebody else was burnt by this feature. :)
>>>
>>>   http://ddili.org/ders/d.en/memory.html#ix_memory.destroy
>>>
>>> Ali
>>
>> I think this is BAD. Why?
>>
>> - it is one of those WAT?? moments that brings a RTFM slap to you. The defaults should not be surprising, and in this case straight dangerous as it can lead to leaks.
>
> Unfortunately, I don't think it's viable to change destroy (see below), it would probably be better to cover this in the dlang tour (if it isn't already).
>
>> - it is not always possible to dereference the pointer, think some circular structures where deref would get you one of those fwd. declaration errors.
>
> In the interest of learning, could you provide an example of such a case?
>
>> - the deprecated delete will call the dtor, destroy is suppose to replace delete - hence it should work the same.
>
> AFAIK destroy isn't supposed to replace delete, since delete is destruction+deallocation and destroy is only destruction; and by that definition they cannot work the same: AFAIR multiple deletes are illegal (since that equals use after free), whereas destroy can be used on the same object as often as you want (the destructor will only be called the first time).
>
>>
>> In my opinion destroy should do this:
>> - call dtor if the pointer type has one defined
>> - nullify the pointer
>>
>> This is what I was expecting anyhow to happen...
>
> This change would be backwards-incompatible and breaks user code, especially manual memory management:
>
> ---
> struct A {}
>
> auto a = cast (A*) malloc(A.sizeof); // Allocate
> emplace(a, 42);                      // Construct
>
> destroy(a);                          // Destruct
> free(a);                             // Deallocate
> ---
>
> if destroy were to already nullify a, how were one supposed to deallocate a?

Here is sample on how destroy fails with a fwd decl error:

struct A
{
    B b;
    C c;
}

struct B
{
    Wrap!A val;
}

struct C
{
    Wrap!A val;
}

struct Wrap(T)
{

    this(bool b)
    {
        t = cast(T*) malloc(T.sizeof);
    }

    ~this()
    {
        destroy(*t);  // Error: struct app.A no size because of forward reference
    }
    T* t;
}

Manual management fails now with the current construct, inst't it?

auto a = cast (A*) malloc(A.sizeof); // Allocate
emplace(a, 42);                      // Construct

destroy(a);                          // Destruct
|-- here a becomes null
assert(a is null); // :}
free(a);                             // Deallocate
|- free null...

You need to save a into a temp, then call free on temp.

A nice to have enhancement would be to return the destroyed pointer from destroy, enabling something like:

destroy(a).free();


February 25, 2017
On Saturday, 25 February 2017 at 13:18:21 UTC, Moritz Maxeiner wrote:
> On Saturday, 25 February 2017 at 13:14:24 UTC, Moritz Maxeiner wrote:
>> ---
>> struct A {}
>>
>> auto a = cast (A*) malloc(A.sizeof); // Allocate
>> emplace(a, 42);                      // Construct
>>
>> destroy(a);                          // Destruct
>> free(a);                             // Deallocate
>> ---
>
> Sorry for double posting, I failed at copy-paste, here's the correct example:
>
> ---
> struct A { int i; }
>
> auto a = cast (A*) malloc(A.sizeof); // Allocate
> emplace(a, 42);                      // Construct
>
> destroy(a);                          // Destruct
> free(a);                             // Deallocate
> ---

The correct way of doing it using deref would to look like:

struct A { int i; }

auto a = cast (A*) malloc(A.sizeof); // Allocate
emplace(a, 42);                      // Construct
destroy(*a);                          // Destruct A
free(a);                             // Deallocate
destroy(a);                          // Destruct A*
assert(a is null);

February 25, 2017
On Saturday, 25 February 2017 at 15:21:56 UTC, Radu wrote:
>
> The correct way of doing it using deref would to look like:
>
> struct A { int i; }
>
> auto a = cast (A*) malloc(A.sizeof); // Allocate
> emplace(a, 42);                      // Construct
> destroy(*a);                          // Destruct A
> free(a);                             // Deallocate
> destroy(a);                          // Destruct A*
> assert(a is null);

Right, I read the post and immediately failed to apply the new knowledge. Bad me, thanks for the correction.
February 25, 2017
On Saturday, 25 February 2017 at 15:13:27 UTC, Radu wrote:
>
> Here is sample on how destroy fails with a fwd decl error:
>
> struct A
> {
>     B b;
>     C c;
> }
>
> struct B
> {
>     Wrap!A val;
> }
>
> struct C
> {
>     Wrap!A val;
> }
>
> struct Wrap(T)
> {
>
>     this(bool b)
>     {
>         t = cast(T*) malloc(T.sizeof);
>     }
>
>     ~this()
>     {
>         destroy(*t);  // Error: struct app.A no size because of forward reference
>     }
>     T* t;
> }

Thanks for the example.

>
> Manual management fails now with the current construct, inst't it?

Hm, that's an issue you'd best take up to the bugtracker, I think. Maybe there's a way around that, but I don't know.

>
> auto a = cast (A*) malloc(A.sizeof); // Allocate
> emplace(a, 42);                      // Construct
>
> destroy(a);                          // Destruct
> |-- here a becomes null
> assert(a is null); // :}
> free(a);                             // Deallocate
> |- free null...
>
> You need to save a into a temp, then call free on temp.
>
> A nice to have enhancement would be to return the destroyed pointer from destroy, enabling something like:
>
> destroy(a).free();

Well, yes, but that is still backwards-incompatible and breaking user code is something I was under the impression was a big NO currently.
February 28, 2017
On Saturday, 25 February 2017 at 16:39:18 UTC, Moritz Maxeiner wrote:
> On Saturday, 25 February 2017 at 15:13:27 UTC, Radu wrote:
>> [...]
>
> Thanks for the example.
>
>> [...]
>
> Hm, that's an issue you'd best take up to the bugtracker, I think. Maybe there's a way around that, but I don't know.
>
>> [...]
>
> Well, yes, but that is still backwards-incompatible and breaking user code is something I was under the impression was a big NO currently.

Made a bug report on the fwd reference error. https://issues.dlang.org/show_bug.cgi?id=17230