www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Surprise destructor call...

reply Manu <turkeyman gmail.com> writes:
Can anyone illuminate me as to where this destructor call is coming from?

struct Thing
{
  static int x;
  this(typeof(null)) pure {} // <-- comment this line and the error goes
away
  ~this() { ++x; }
}

Thing fun() pure
{
   return Thing(null);
}

error : `pure` function `urt.string.string.fun` cannot call impure
destructor `urt.string.string.Thing.~this`

Shouldn't NVRO construct the result in place and elide the copy/move? I
would not expect any call to the destructor in fun()...
Also surprisingly, if I comment out the constructor, the compile error
about the destructor goes away. I can't see why the constructor's existence
affects the destruction semantics in fun()?
Aug 14
parent reply Dennis <dkorpel gmail.com> writes:
On Wednesday, 14 August 2024 at 09:19:55 UTC, Manu wrote:
 Also surprisingly, if I comment out the constructor, the 
 compile error
 about the destructor goes away. I can't see why the 
 constructor's existence
 affects the destruction semantics in fun()?
The compiler's logic goes as follows: ```D return Thing(null); ``` Since `Thing` has a constructor, this gets rewritten to: ```D return Thing().this(null); ``` Now we have a 'DotVarExp' with a struct literal on the left, and the struct has a destructor. Since internally expression temporaries can't have destructors, it gets extracted into a temporary variable: ```D return ((Thing __slThing3 = Thing();) , __slThing3).this(null); ``` Then `__slThing3` goes out of scope and needs a destructor call. So clearly, in this case you don't want the temporary, but in other cases (`return Thing(null).field;`) you do need it, so I'm thinking about what the right conditions should be for the temporary.
Aug 14
next sibling parent reply user1234 <user1234 12.de> writes:
On Wednesday, 14 August 2024 at 11:52:51 UTC, Dennis wrote:
 [...]
 Now we have a 'DotVarExp' with a struct literal on the left, 
 and the struct has a destructor. Since internally expression 
 temporaries can't have destructors, it gets extracted into a 
 temporary variable:

 ```D
 return ((Thing __slThing3 = Thing();) , __slThing3).this(null);
 ```

 Then `__slThing3` goes out of scope and needs a destructor call.

 So clearly, in this case you don't want the temporary, but in 
 other cases (`return Thing(null).field;`) you do need it, so 
 I'm thinking about what the right conditions should be for the 
 temporary.
The hidden temporary is not represented in the -vcg-ast output. That would have helped to understand the issue.
Aug 14
parent reply Dennis <dkorpel gmail.com> writes:
On Wednesday, 14 August 2024 at 12:42:13 UTC, user1234 wrote:
 The hidden temporary is not represented in the -vcg-ast output. 
 That would have helped to understand the issue.
https://github.com/dlang/dmd/pull/16782
Aug 14
parent user1234 <user1234 12.de> writes:
On Wednesday, 14 August 2024 at 13:25:18 UTC, Dennis wrote:
 On Wednesday, 14 August 2024 at 12:42:13 UTC, user1234 wrote:
 The hidden temporary is not represented in the -vcg-ast 
 output. That would have helped to understand the issue.
https://github.com/dlang/dmd/pull/16782
let's get the merge 🤙
Aug 14
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On Wed, 14 Aug 2024 at 21:56, Dennis via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Wednesday, 14 August 2024 at 09:19:55 UTC, Manu wrote:
 Also surprisingly, if I comment out the constructor, the
 compile error
 about the destructor goes away. I can't see why the
 constructor's existence
 affects the destruction semantics in fun()?
The compiler's logic goes as follows: ```D return Thing(null); ``` Since `Thing` has a constructor, this gets rewritten to: ```D return Thing().this(null); ``` Now we have a 'DotVarExp' with a struct literal on the left, and the struct has a destructor. Since internally expression temporaries can't have destructors, it gets extracted into a temporary variable: ```D return ((Thing __slThing3 = Thing();) , __slThing3).this(null); ``` Then `__slThing3` goes out of scope and needs a destructor call. So clearly, in this case you don't want the temporary, but in other cases (`return Thing(null).field;`) you do need it, so I'm thinking about what the right conditions should be for the temporary.
Well, the condition is that NRVO should elide the copy/move... it should be constructed at the caller's scope in this case. Your example `Thing(null).field` is not the same thing, because it's not NRVO at all. I guess this is a bug then?
Aug 14
parent reply kinke <noone nowhere.com> writes:
On Wednesday, 14 August 2024 at 13:56:08 UTC, Manu wrote:
 Well, the condition is that NRVO should elide the copy/move... 
 it should be
 constructed at the caller's scope in this case.
 Your example `Thing(null).field` is not the same thing, because 
 it's not
 NRVO at all.

 I guess this is a bug then?
When actually using NRVO (`auto r = Thing(null); return r;`), one even gets the error *twice*. So that's definitely a bug, the destruction is handled by the caller; just for the attributes check etc., there's no real dtor call in `fun()`. What you have is a case for RVO; AFAIK, LDC and GDC implement that (for non-POD types at least), no idea about DMD. Meaning that the temporary in the return expression isn't destructed by `foo` either; it's emplaced directly into the caller-allocated return value, as the NRVO case.
Aug 14
parent Manu <turkeyman gmail.com> writes:
On Thu, 15 Aug 2024 at 00:31, kinke via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Wednesday, 14 August 2024 at 13:56:08 UTC, Manu wrote:
 Well, the condition is that NRVO should elide the copy/move...
 it should be
 constructed at the caller's scope in this case.
 Your example `Thing(null).field` is not the same thing, because
 it's not
 NRVO at all.

 I guess this is a bug then?
When actually using NRVO (`auto r = Thing(null); return r;`), one even gets the error *twice*. So that's definitely a bug, the destruction is handled by the caller; just for the attributes check etc., there's no real dtor call in `fun()`. What you have is a case for RVO; AFAIK, LDC and GDC implement that (for non-POD types at least), no idea about DMD. Meaning that the temporary in the return expression isn't destructed by `foo` either; it's emplaced directly into the caller-allocated return value, as the NRVO case.
My understanding is that RVO is in the D spec and NOT simply an optimisation. The language shouldn't attempt to call a destructor here in my case under any circumstances... Walter?
Aug 14