digitalmars.D - Surprise destructor call...
- Manu (19/19) Aug 14 Can anyone illuminate me as to where this destructor call is coming from...
- Dennis (21/26) Aug 14 The compiler's logic goes as follows:
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
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
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
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
On Wednesday, 14 August 2024 at 13:25:18 UTC, Dennis wrote:On Wednesday, 14 August 2024 at 12:42:13 UTC, user1234 wrote:let's get the merge 🤙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
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: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?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
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
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: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?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