Closures and loop scope

Timon Gehr timon.gehr at gmx.ch
Tue Jun 4 15:16:48 PDT 2013


On 06/04/2013 11:57 PM, Nick Sabalausky wrote:
> On Tue, 04 Jun 2013 22:52:27 +0200
> Timon Gehr <timon.gehr at gmx.ch> wrote:
>
>> On 06/04/2013 09:19 PM, Idan Arye wrote:
>>> Consider the following code. What will it print?
>>>
>>>       auto arr=new ulong delegate()[5];
>>>
>>>       foreach(i;0..arr.length){
>>>           arr[i]=()=>i;
>>>       }
>>>
>>>       writeln(arr.map!`a()`());
>>>
>>> It is natural to expect that it will print [0, 1, 2, 3, 4], but it
>>> actually prints [5, 5, 5, 5, 5]. The reason is obvious - all the
>>> delegates refer to the same `i` in their closures, and at the end
>>> of the loop that `i` is set to `5`.
>>>
>>> ...
>>
>> It is not that obvious. They refer to different i's that happen to
>> reside at the same place in the stack frame. It's a bug.
>>
>> It is more obvious that it is a bug given this code snippet:
>>
>> import std.stdio, std.algorithm;
>>
>> void main(){
>>       auto arr=new ulong delegate()[5];
>>       foreach(immutable i;0..arr.length){
>>            arr[i]={auto j=i;return {assert(j==i); return i;};}();
>>       }
>>       writeln(arr.map!`a()`());
>> }
>>
>> As you can see, 'i' mutates even though it is declared immutable.
>>
>
> Keep in mind that this exhibits the same "mutating immutable" behavior:
>
> // Prints 0, 1, 2, 3, 4, even though i is immutable
> foreach(immutable i; 0..5){
>      writeln(i);
>


No, it does not! My code contains immutable variables j and i (in fact, 
5 pairs of these), where with DMD's buggy behaviour it appears that at 
one point in time j==i and at another point in time j!=i.

Your code contains five distinct immutable variables i with different 
values.

> It's questionable as to whether that's really a problem. And even if it
> is a problem, it would *only* be because you make i immutable. So
> this is irrelevant to the OP's examples because:
>
> 1. He didn't use immutable, and
> ...

No, this point is irrelevant. It is a bug in any case. If immutable is 
used, this bug can be exploited to break the const system. Hence, I was 
using immutable to make my point more clear. Apparently it had the 
converse effect.

> 2. A delegate is *expected* to read the values inside its closure at
> the time of delegate *invocation*, not at the time of delegate creation.
>

Obviously. What you seem to miss is that there is no aliasing between 
any of the delegate closures in the above code.

Note that I understand exactly what you think is happening.

>>> ...
>>
>> Yes. Yes.
>>
>> http://d.puremagic.com/issues/show_bug.cgi?id=2043
>
> That refers to a local defined within the loop, not the iteration
> variable itself, which is different from the OP.

No, it is not, as I have explained before. Since 2.063, the loop 
variable that is exposed from the foreach loop behaves like a for-loop 
loop body-local variable.

> Also, it's
> questionable that there's any problem there *other* than the immutable
> stuff.
>

Obviously there is. The only reason why the code appears to behave as 
you assume it does is because the loop-local variables happen to be 
allocated at the same place for each iteration. It's a memory safety 
issue. Allocated memory is allocated again before it is freed.

If that helps, other C-like languages with closure support (eg. C#) 
correctly allocate a closure context per loop iteration.



More information about the Digitalmars-d mailing list