Thread overview
Nasty corner case behaviour
Apr 18, 2019
H. S. Teoh
Apr 19, 2019
Suleyman
Apr 19, 2019
Suleyman
Apr 19, 2019
Meta
Apr 20, 2019
Suleyman
April 17, 2019
Got bitten by this today:

	import std.algorithm : filter, map;
	import std.stdio;

	struct S {
		bool flag;
		auto method() {
			return [ 1 ].filter!(e => flag);
		}
	}

	void main() {
		auto s = S(true);
		writeln(s.method);

		auto arr = [ s ];
		writeln(arr[0].method);

		auto mappedArray = arr.map!(e => e.method);
		writeln(mappedArray.front);
		writeln(mappedArray);
	}

Expected output:

	[1]
	[1]
	[1]
	[[1]]

Actual output:

	[1]
	[1]
	[1]
	[[]]

Exercise for the reader: explain the problem.

Hint: it's not a bug in Phobos.

If method() is changed to the following, the problem goes away:

	auto method() {
		auto _flag = flag;
		return [ 1 ].filter!(e => _flag);
	}

It should be obvious by now that the problem is caused by the lambda incorrectly closing over a member variable in a struct instance that subsequently goes out of scope, while the returned closure lives on. Unfortunately, neither -dip25 nor -dip1000 catches this leakage, even if main() is marked @safe.


T

-- 
Тише едешь, дальше будешь.
April 19, 2019
On Thursday, 18 April 2019 at 05:01:17 UTC, H. S. Teoh wrote:
> ...

The value of flag is lost.

To easily see this make flag an int.

   import std.algorithm : filter, map;
   import std.stdio;

   struct S {
       int flag;
       auto method() {
           return [ 1 ].filter!((e) {
                           writeln("\nflag: ", flag);
                           return flag == 10;
                       });
       }
   }

   void main() {
       auto s = S(10);

       auto arr = [ s ];

       auto mappedArray = arr.map!(e => e.method);
       writeln(mappedArray);
   }

 Expected output:

       [[
       flag: 10
       1]]

 Actual output:

       [[
       flag: 1872694912
       ]]


April 18, 2019
On 4/18/19 1:01 AM, H. S. Teoh wrote:
> Got bitten by this today:
> 

Yeeouch! Bugzilla issue #?

April 19, 2019
On Thursday, 18 April 2019 at 05:01:17 UTC, H. S. Teoh wrote:
> ...

Sneaky enough.
The problem is the value copy semantics of S which results in an address to a dead stack frame being saved.
If you make it a ref parameter the issue goes away.

   import std.algorithm : filter, map;
   import std.stdio;

   struct S {
       bool flag;
       auto method() {
           return [ 1 ].filter!(e => flag);
       }
   }

   void main() {
       auto s = S(true);
       writeln(s.method);

       auto arr = [ s ];
       writeln(arr[0].method);

       auto mappedArray = arr.map!((ref e) => e.method);
       writeln(mappedArray.front);
       writeln(mappedArray);
   }

 Output:

   [1]
   [1]
   [1]
   [[1]]

April 19, 2019
On Thursday, 18 April 2019 at 05:01:17 UTC, H. S. Teoh wrote:
> Got bitten by this today:
>
> 	import std.algorithm : filter, map;
> 	import std.stdio;
>
> 	struct S {
> 		bool flag;
> 		auto method() {
> 			return [ 1 ].filter!(e => flag);
> 		}
> 	}
>
> 	void main() {
> 		auto s = S(true);
> 		writeln(s.method);
>
> 		auto arr = [ s ];
> 		writeln(arr[0].method);
>
> 		auto mappedArray = arr.map!(e => e.method);
> 		writeln(mappedArray.front);
> 		writeln(mappedArray);
> 	}
>
> Expected output:
>
> 	[1]
> 	[1]
> 	[1]
> 	[[1]]
>
> Actual output:
>
> 	[1]
> 	[1]
> 	[1]
> 	[[]]
>
> Exercise for the reader: explain the problem.
>
> Hint: it's not a bug in Phobos.
>
> If method() is changed to the following, the problem goes away:
>
> 	auto method() {
> 		auto _flag = flag;
> 		return [ 1 ].filter!(e => _flag);
> 	}
>
> It should be obvious by now that the problem is caused by the lambda incorrectly closing over a member variable in a struct instance that subsequently goes out of scope, while the returned closure lives on. Unfortunately, neither -dip25 nor -dip1000 catches this leakage, even if main() is marked @safe.
>
>
> T

I find this one a bit confusing. Shouldn't the struct be moved to the heap when the compiler detects that it's being closed over?
April 20, 2019
On Friday, 19 April 2019 at 23:42:53 UTC, Meta wrote:
> I find this one a bit confusing. Shouldn't the struct be moved to the heap when the compiler detects that it's being closed over?

It does. the `this` parameter in the following function is copied to a heap closure space.

	auto method() {
 		return [ 1 ].filter!(e => this.flag);
 	}

But `this` is a reference so the pointer is copied instead of the value.