digitalmars.D - =?UTF-8?B?4oCcT3V04oCd?= parameters and destructors
- Ogion (40/40) Mar 14 Functions with `out` parameters simply initialize the parameter
- =?UTF-8?Q?Ali_=C3=87ehreli?= (12/14) Mar 14 It took me a while to understand the point. In other words:
- Ogi (26/32) Mar 15 I want to clarify if this is intentional or not, and what is the
- Nick Treleaven (3/6) Mar 17 Thanks, PR:
- Salih Dincer (41/53) Mar 20 I think you're exaggerating the issue more than it is! Who is
- Manfred Nowak (9/16) Mar 15 If one moves the line containing 'writeln´ one line up before the
- Ogion (29/33) Mar 15 One who has eyes, let one see:
- Manfred Nowak (6/7) Mar 16 Clear proof that the 'opAsssign´, specified in the docs at 15.20,
- Nick Treleaven (12/14) Mar 17 See https://github.com/dlang/dmd/issues/18348.
- Ogion (9/14) Mar 17 Understandable.
- matheus (4/13) Mar 18 Wondering if that's the reason why in C# you need to construct an
- Basile B. (8/11) Mar 18 According to me that's clearly a bug. The destructor should be
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
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
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
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
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: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 79Although 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.
Mar 20
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:If there is no 'opAssign´ in 'S´ the compiler might insert a default. If a default is inserted, does it suffice?t.opAssign(s);
Mar 15
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
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
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
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
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:object before passing to an out parameter. Matheus.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 ...
Mar 18
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