www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why is dtor called for lazy parameter?

reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
I have this code:
==========
class S
{
     int i = -1;

     this(int n) { i = n; writeln(i," ",__PRETTY_FUNCTION__); }
     ~this() { writeln(i," ",__PRETTY_FUNCTION__); }
}


auto create()
{
     writeln("-> ",__PRETTY_FUNCTION__);
     scope(exit) writeln("<- ",__PRETTY_FUNCTION__);

     return scoped!S(1);
}

auto do_something(S s)
{
     writeln("-> ",s.i," ",__PRETTY_FUNCTION__);
     scope(exit) writeln("<- ",s.i," ",__PRETTY_FUNCTION__);

     return s;
}

auto do_lazy(lazy S s)
{
     writeln("-> ",__PRETTY_FUNCTION__);
     scope(exit) writeln("<- ",__PRETTY_FUNCTION__);

     return s;
}

void main()
{
     writeln("-> ",__PRETTY_FUNCTION__);
     scope(exit) writeln("<- ",__PRETTY_FUNCTION__);

     create()
     .do_lazy()
     .do_something();
}
==========
This is the output:
==========
-> void test.main()
-> test.do_lazy(lazy S s)
-> test.create()
1 S test.S.this(int n)
<- test.create()
1 void test.S.~this()                (1)
<- test.do_lazy(lazy S s)
-> 1703096 test.do_something(S s)    (2)
<- 1703104 test.do_something(S s)    (3)
<- void test.main()
==========

Why is dtor called before returning from do_lazy function - see 
(1)? It seems cause uninitialized parameter in do_something call 
after it in (2)-(3).
Sep 18 2020
next sibling parent reply ikod <igor.khasilev gmail.com> writes:
On Friday, 18 September 2020 at 10:43:47 UTC, Andrey Zherikov 
wrote:
 I have this code:
 ==========
 class S
...
 auto create()
 {
     writeln("-> ",__PRETTY_FUNCTION__);
     scope(exit) writeln("<- ",__PRETTY_FUNCTION__);

     return scoped!S(1);
 }
If you replace this line with "return new S(1)" it should work as you expect.
Sep 18 2020
parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Friday, 18 September 2020 at 10:54:41 UTC, ikod wrote:
 On Friday, 18 September 2020 at 10:43:47 UTC, Andrey Zherikov 
 wrote:
 I have this code:
 ==========
 class S
...
 auto create()
 {
     writeln("-> ",__PRETTY_FUNCTION__);
     scope(exit) writeln("<- ",__PRETTY_FUNCTION__);

     return scoped!S(1);
 }
If you replace this line with "return new S(1)" it should work as you expect.
This works here, but doesn't meet my intention in main(). I want something to be called automatically as a final step when "create().do_lazy().do_something();" is done and I see two ways to do this: either use struct as return type (this unfortunately forces copy struct value for every function call) or use scoped!(class) - so dtor is called right after the expression. In case of "new S(1)" dtor is called after exit from main().
Sep 18 2020
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Friday, 18 September 2020 at 10:43:47 UTC, Andrey Zherikov 
wrote:
 Why is dtor called before returning from do_lazy function - see 
 (1)? It seems cause uninitialized parameter in do_something 
 call after it in (2)-(3).
As ikod mentions, it's because of scoped!S. As for why it does this, yeah, this is kinda egregious. The documentation[0] of scoped mentions this issue:
 This facility is unsafe; it is the responsibility of the user 
 to not escape a reference to the object outside the scope.
As the name implies, scoped!T limits the valid scope of a reference to the scope of the variable holding it. You're putting it on the stack, so the moment the variable goes off the stack (when do_lazy returns), the reference is destructed. -- Simen [0]: https://dlang.org/library/std/typecons/scoped.html
Sep 18 2020
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Friday, 18 September 2020 at 11:31:39 UTC, Simen Kjærås wrote:
 On Friday, 18 September 2020 at 10:43:47 UTC, Andrey Zherikov 
 wrote:
 Why is dtor called before returning from do_lazy function - 
 see (1)? It seems cause uninitialized parameter in 
 do_something call after it in (2)-(3).
As ikod mentions, it's because of scoped!S. As for why it does this, yeah, this is kinda egregious. The documentation[0] of scoped mentions this issue:
 This facility is unsafe; it is the responsibility of the user 
 to not escape a reference to the object outside the scope.
As the name implies, scoped!T limits the valid scope of a reference to the scope of the variable holding it. You're putting it on the stack, so the moment the variable goes off the stack (when do_lazy returns), the reference is destructed. -- Simen [0]: https://dlang.org/library/std/typecons/scoped.html
I modified do_lazy in this way: ====== auto do_lazy(lazy S s) { writeln("-> ",__PRETTY_FUNCTION__); scope(exit) writeln("<- ",__PRETTY_FUNCTION__); auto s1 = s; writeln("===",s1.i); (1) return s1; } ====== The output is: ====== -> void test.main() -> test.do_lazy(lazy S s) -> test.create() 1 S test.S.this(int n) <- test.create() 1 void test.S.~this() ===-1 (2) <- test.do_lazy(lazy S s) -> 1703096 test.do_something(S s) <- 1703096 test.do_something(S s) <- void test.main() ====== As you can see, dtor is called before writeln on (1) and s1.i is -1 (2)
Sep 18 2020
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Friday, 18 September 2020 at 12:32:49 UTC, Andrey Zherikov 
wrote:
 ======
 The output is:
 ======
 -> void test.main()
 -> test.do_lazy(lazy S s)
 -> test.create()
 1 S test.S.this(int n)
 <- test.create()
 1 void test.S.~this()
 ===-1                          (2)
 <- test.do_lazy(lazy S s)
 -> 1703096 test.do_something(S s)
 <- 1703096 test.do_something(S s)
 <- void test.main()
 ======

 As you can see, dtor is called before writeln on (1) and s1.i 
 is -1 (2)
Indeed. As we can see from the output, first do_lazy() is called from test.main, then create() is called (this happens inside do_lazy, as s is lazy). When create() returns, the scoped!S you created goes out of scope and is destroyed. scoped's destructor overwrites the memory with S.init, which is why s.i is -1 at that point. Then the memory is overwritten by subsequent function calls, as that stack space is now considered vacant. That's why the output changes from -1 to 1703096. A bit interesting is the fact that <- test.create() is printed before ~this(). I expected the order to be opposite, but there may be sensible reasons why it's not. scoped!S is only valid inside the scope its variable exists in, and when that scope is exited, it refers to random stack data. It's a lot like this code: int* fun() { int a; int* p = &a; return p; } Note that return &a; does not compile, with a warning about escaping a reference to a local variable. That's exactly the same thing that's happening with scoped!S, but since scoped is more complex, the compiler has a hard time keeping track of things, and code that in a perfect world would not compile, does. It may be that D's scope tracking functionality has become good enough to catch this error now, if the functions are properly marked. Even if this is the case, then they are obviously not properly marked. :p Now, this begets the question: *when* should I use scoped!T? Short answer: Basically never. Longer answer: 1) When the lifetime of one object needs to be a strict subset of another. That is, the class instance you created only exists as long as the function create() is on the stack. When scoped!T is a member of another class or struct, it continues to live as long as that object exists. In most cases, you don't mind that it stays around for longer, and can let the GC handle the cleanup. If you really do care, you can use scoped!T, or explicitly destroy the object when you're done with it. 2) scoped!T may be used as a performance hack, letting you avoid the GC. If you have instrumented your code and found that this is the culprit, scoped!T might help. Even if GC is the problem, There may be other cases, but I believe those are the main two reasons to use scoped!T. -- Simen
Sep 18 2020
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Friday, 18 September 2020 at 13:28:39 UTC, Simen Kjærås wrote:
 Indeed. As we can see from the output, first do_lazy() is 
 called from test.main, then create() is called (this happens 
 inside do_lazy, as s is lazy). When create() returns, the 
 scoped!S you created goes out of scope and is destroyed.
It seems that dtor is called at exit from lazy delegate, not at exit form create(): ====== create() .do_something() .do_lazy() .do_something(); ====== Output: ====== -> void test.main() -> test.do_lazy(lazy S s) -> test.create() 1 S test.S.this(int n) <- test.create() -> 1 test.do_something(S s) <- 1 test.do_something(S s) 1 void test.S.~this() ===-1 <- test.do_lazy(lazy S s) -> 1703096 test.do_something(S s) <- 1703096 test.do_something(S s) <- void test.main() ====== This doesn't even allow me to copy the value of 's' in do_lazy().
Sep 18 2020
parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Friday, 18 September 2020 at 14:14:31 UTC, Andrey Zherikov 
wrote:
 It seems that dtor is called at exit from lazy delegate, not at 
 exit form create():
 ======
     create()
     .do_something()
     .do_lazy()
     .do_something();
 ======
 Output:
 ======
 -> void test.main()
 -> test.do_lazy(lazy S s)
 -> test.create()
 1 S test.S.this(int n)
 <- test.create()
 -> 1 test.do_something(S s)
 <- 1 test.do_something(S s)
 1 void test.S.~this()
 ===-1
 <- test.do_lazy(lazy S s)
 -> 1703096 test.do_something(S s)
 <- 1703096 test.do_something(S s)
 <- void test.main()
 ======

 This doesn't even allow me to copy the value of 's' in 
 do_lazy().
You're right, I missed a step: do_lazy() takes a S, not a scoped!S, so the conversion from scoped!S to S happens after create() has returned and before the value is used in do_lazy. This explains my confusion earlier. D's lazy is essentially the same as a delegate or function, so you could* rewrite to this (writelns omitted for clarity): S do_lazy(S function() s) { return s(); } void main() { (() => cast(S)create()) // Here .do_lazy() .do_something(); } On the marked line, the cast from scoped!S to S happens, the scoped!S goes out of scope and the destructor is called. *There may be differences, but for this discussion these are not important. -- Simen
Sep 18 2020
prev sibling parent reply drug <drug2004 bk.ru> writes:
Can't you put a var on the stack?
```
auto instance = create();
instance
     	.do_lazy()
     	.do_something();
```
Sep 18 2020
parent drug <drug2004 bk.ru> writes:
On 9/18/20 4:30 PM, drug wrote:
 Can't you put a var on the stack?
 ```
 auto instance = create();
 instance
          .do_lazy()
          .do_something();
 ```
Ah, I totally missed your point. Now I see you can't...
Sep 18 2020