www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - =?UTF-8?B?4oCcT3V04oCd?= parameters and destructors

reply Ogion <ogion.art gmail.com> writes:
Functions with `out` parameters simply initialize the parameter 
with `.init`, instead of properly destroying it.

```D
import std.stdio;

int count;

struct S {
     bool initialized;
     this(int) {
         writeln("S()");
         initialized = true;
         count++;
     }
     ~this() {
         if (initialized) {
             writeln("~S()");
             count--;
         }
     }
}

void foo(out S s) {
     s = S(42);
}

void bar() {
     S s;
     foo(s);
     foo(s);
}

void main() {
     bar();
     writeln(count);
}
```

Output:
```
S()
S()
~S()
1
```

Looks like a serious oversight.
Mar 14
next sibling parent reply =?UTF-8?Q?Ali_=C3=87ehreli?= <acehreli yahoo.com> writes:
On 3/14/25 12:30 AM, Ogion wrote:
 Functions with `out` parameters simply initialize the parameter with
 `.init`, instead of properly destroying it.
It took me a while to understand the point. In other words: auto s = S(43); foo(s); The destructor of 'S(43)' is never called. Although understandably undesirable, this is according to spec: https://dlang.org/spec/function.html#ref-params "An out parameter [...] is initialized with x.init upon function invocation." Perhaps the compiler should not allow using constructed objects as 'out' parameter arguments. Ali
Mar 14
parent reply Ogi <ogion.art gmail.com> writes:
On Friday, 14 March 2025 at 16:54:10 UTC, Ali Çehreli wrote:
 Although understandably undesirable, this is according to spec:

   https://dlang.org/spec/function.html#ref-params

 "An out parameter [...] is initialized with x.init upon 
 function invocation."
I want to clarify if this is intentional or not, and what is the intention if it is. Currently, it turns out that the variable has to be destroyed before passing it as an “out” argument. It’s counter-intuitive, and it defeats the purpose of “out” parameters.
 Perhaps the compiler should not allow using constructed objects 
 as 'out' parameter arguments.
In this aspect, current implementation differs from spec, so it handles constructed objects correctly. The “out” parameter is not initialized with `.init`, it’s *default-initialized*: ```D void main() { import std.stdio; int x; struct S { void bumpX() { x++; } } void foo(out S s) {} S s = S.init; foo(s); s.bumpX(); writeln(x); } ``` If `S` would be initialized with `.init`, the hidden context pointer in the nested struct would be null, and calling to `bumpX()` would crash the program. But it doesn’t crash and prints: `1`.
Mar 15
next sibling parent Nick Treleaven <nick geany.org> writes:
On Saturday, 15 March 2025 at 11:44:14 UTC, Ogi wrote:
 In this aspect, current implementation differs from spec, so it 
 handles constructed objects correctly. The “out” parameter is 
 not initialized with `.init`, it’s *default-initialized*:
Thanks, PR: https://github.com/dlang/dlang.org/pull/4190
Mar 17
prev sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Saturday, 15 March 2025 at 11:44:14 UTC, Ogi wrote:
 On Friday, 14 March 2025 at 16:54:10 UTC, Ali Çehreli wrote:
 Although understandably undesirable, this is according to spec:

   https://dlang.org/spec/function.html#ref-params

 "An out parameter [...] is initialized with x.init upon 
 function invocation."
I want to clarify if this is intentional or not, and what is the intention if it is. Currently, it turns out that the variable has to be destroyed before passing it as an “out” argument. It’s counter-intuitive, and it defeats the purpose of “out” parameters.
I think you're exaggerating the issue more than it is! Who is going to use a local variable in their code? Let's look at the issue like this: ```d enum check = 40; struct S {    int num = check;   auto inc2() => num += 2; } void foo(out S s) in (s.num == check) {    assert(s.num == check); } import std.stdio; void main() {    S s; s.foo(); s.inc2();    s.num.writeln; // 42        void bar(out S s)    in (s.num == check) {        assert(s.num == check); }    bar(s);    s.num.writeln; // 40 } ``` The issue arises in the bar(s); call. Although s.num was incremented to 42 before bar(s), the out parameter in D resets s to its default state before entering bar(). Since s.num is initialized to check (which is 40), the assertion inside bar() holds, and after the function call, s.num is reset to 40. This explains why s.num.writeln; prints 40 after bar(s), effectively discarding the previous increment. SDB 79
Mar 20
prev sibling next sibling parent reply Manfred Nowak <svv1999 hotmail.com> writes:
On Friday, 14 March 2025 at 07:30:14 UTC, Ogion wrote:
[...]
     ~this() {
         if (initialized) {
             writeln("~S()");
If one moves the line containing 'writeln´ one line up before the 'if´-condition, then one might be notified of all calls---and one might be able to recognize more than one call of the destructor. [...]
     s = S(42);
The docs at 15.20.2 look very similar to:
Struct assignment 't=s´ is defined to be semantically equivalent 
to:
   t.opAssign(s);
If there is no 'opAssign´ in 'S´ the compiler might insert a default. If a default is inserted, does it suffice?
Mar 15
parent reply Ogion <ogion.art gmail.com> writes:
On Saturday, 15 March 2025 at 20:21:41 UTC, Manfred Nowak wrote:
 If one moves the line containing 'writeln´ one line up before 
 the 'if´-condition, then one might be notified of all 
 calls---and one might be able to recognize more than one call 
 of the destructor.
One who has eyes, let one see: ```D import std.stdio; struct S { int x; this(int x) { this.x = x; writeln("S(", x, ")"); } ~this() { writeln("~S(", x, ")"); } } void foo(out S s) {} void main() { S s = S(42); foo(s); } ``` Output: ``` S(42) ~S(0) ``` Alas, the object instantiated with `S(42)` was not given the proper burial. The treacherous function replaced its body with an empty shell. Its restless spirit will wander the earth for all eternity.
Mar 15
parent Manfred Nowak <svv1999 hotmail.com> writes:
On Saturday, 15 March 2025 at 23:39:36 UTC, Ogion wrote:
[...]
 Its restless spirit will wander the earth for all eternity.
Clear proof that the 'opAsssign´, specified in the docs at 15.20, was not called---or the call of the '__dtor´ within that 'opAssign´ does not give any due respect to the specified '~this´. -manfred
Mar 16
prev sibling next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Friday, 14 March 2025 at 07:30:14 UTC, Ogion wrote:
 Functions with `out` parameters simply initialize the parameter 
 with `.init`, instead of properly destroying it.
See https://github.com/dlang/dmd/issues/18348. I think the solution is to always call the destructor before calling the function. However that was not implemented because it would break code that uses `S s = void;`, as `s` might not be a destructible value. The `= void` case could perhaps be supported if the compiler was smart enough to detect simple cases where `s` wasn't constructed. In those cases calling the destructor could be omitted (the spec might need updating to allow that). Given that all this could still break existing code with non-simple control flow, it would probably need to be done in the next edition rather than current edition of the language.
Mar 17
next sibling parent Ogion <ogion.art gmail.com> writes:
On Monday, 17 March 2025 at 11:40:59 UTC, Nick Treleaven wrote:
 See https://github.com/dlang/dmd/issues/18348.

 I think the solution is to always call the destructor before 
 calling the function. However that was not implemented because 
 it would break code that uses `S s = void;`, as `s` might not 
 be a destructible value.
Understandable. I should note that spec never said that the parameter is expected to be uninitialized. However, the fact that some some code relies on this behavior can’t be ignored. I don’t think that “out” parameters worth fixing though. They only exist because we don’t have first-class tuples. Once we sort this out, we’ll just deprecate the “out” parameters with all their warts.
Mar 17
prev sibling parent matheus <matheus gmail.com> writes:
On Monday, 17 March 2025 at 11:40:59 UTC, Nick Treleaven wrote:
 On Friday, 14 March 2025 at 07:30:14 UTC, Ogion wrote:
 Functions with `out` parameters simply initialize the 
 parameter with `.init`, instead of properly destroying it.
See https://github.com/dlang/dmd/issues/18348. I think the solution is to always call the destructor before calling the function. However that was not implemented because it would break code that uses `S s = void;`, as `s` might not be a destructible value. The `= void` case could perhaps be supported if the compiler was smart enough to detect simple ...
object before passing to an out parameter. Matheus.
Mar 18
prev sibling parent Basile B. <b2.temp gmx.com> writes:
On Friday, 14 March 2025 at 07:30:14 UTC, Ogion wrote:
 Functions with `out` parameters simply initialize the parameter 
 with `.init`, instead of properly destroying it.
 [...]
According to me that's clearly a bug. The destructor should be called. Otherwise you end up, for example, with user-types relying on ref counting that dont work anymore. That being said, `out` parameters just exist because of languages that dont guarante that what's passed in is well defined. That's another debate.
Mar 18