Strange closure behaviour

Timon Gehr timon.gehr at gmx.ch
Sun Jun 16 01:36:38 UTC 2019


On 15.06.19 18:29, Rémy Mouëza wrote:
> On Saturday, 15 June 2019 at 01:21:46 UTC, Emmanuelle wrote:
>> On Saturday, 15 June 2019 at 00:30:43 UTC, Adam D. Ruppe wrote:
>>> On Saturday, 15 June 2019 at 00:24:52 UTC, Emmanuelle wrote:
>>>> Is it a compiler bug?
>>>
>>> Yup, a very longstanding bug.
>>>
>>> You can work around it by wrapping it all in another layer of 
>>> function which you immediately call (which is fairly common in 
>>> javascript):
>>>
>>>         funcs ~= ((x) => (int i) { nums[x] ~= i; })(x);
>>>
>>> Or maybe less confusingly written long form:
>>>
>>>         funcs ~= (delegate(x) {
>>>             return (int i) { nums[x] ~= i; };
>>>         })(x);
>>>
>>> You write a function that returns your actual function, and 
>>> immediately calls it with the loop variable, which will explicitly 
>>> make a copy of it.
>>
>> Oh, I see. Unfortunate that it's a longstanding compiler bug, but at 
>> least the rather awkward workaround will do. Thank you!
> 
> I don't know if we can tell this is a compiler bug.

It's a bug. It's memory corruption. Different objects with overlapping 
lifetimes use the same memory location.

> The same behavior happens in Python.

No, it's not the same. Python has no sensible notion of variable scope.

 >>> for i in range(3): pass
...
 >>> print(i)
2

Yuck.

> The logic being variable `x` is captured by the 
> closure. That closure's context will contain a pointer/reference to x. 
> Whenever x is updated outside of the closure, the context still points 
> to the modified x. Hence the seemingly strange behavior.
> ...

It's not the same instance of the variable. Foreach loop variables are 
local to the loop body. They may both be called `x`, but they are not 
the same. It's most obvious with `immutable` variables.

> Adam's workaround ensures that the closure captures a temporary `x` 
> variable on the stack: a copy will be made instead of taking a 
> reference, since a pointer to `x` would be dangling once the 
> `delegate(x){...}` returns.
> 
> Most of the time, we want a pointer/reference to the enclosed variables 
> in our closures. Note that C++ 17 allows one to select the capture mode: 
> the following link lists 8 of them: 
> https://en.cppreference.com/w/cpp/language/lambda#Lambda_capture.
> ...

No, this is not an issue of by value vs by reference. All captures in D 
are by reference, yet the behavior is wrong.

> D offers a convenient default that works most of the time. The trade-off 
> is having to deal with the creation of several closures referencing a 
> variable being modified in a single scope, like the incremented `x` of 
> the for loop.
> ...

By reference capturing may be a convenient default, but even capturing 
by reference the behavior is wrong.





More information about the Digitalmars-d-learn mailing list