Search code examples
loopsdelegatesclosuresd

Closures in loops capturing by reference?


Delegates in D seem to capture local values by reference, which has weird side effects when creating closures in loops: In the end, you have n closures with equal context pointers. Take this example:

import std.stdio;
alias Closure = void delegate();
Closure[] closures;

void main(){
    foreach(a; ["Je", "Tu", "En", "Oui", "Na"])
        closures ~= {write(a);};
    foreach(c; closures)
        c();
    writeln(" batman");
}

This prints NaNaNaNaNa batman.

Is this expected behavior? If so, how would I work around it so it properly prints all array elements?

It gets even funnier when using a for-loop with counter variable, in the end i is equal to the array size, and when using closures[i] in the delegate it throws an out of bounds error.


Solution

  • Yes, that is expected behavior (EDIT: ...it is actually an oldstanding known bug! https://issues.dlang.org/show_bug.cgi?id=2043 so only expected in that it happens and you can easily get used to it, but it actually isn't supposed to happen) and is also seen in other languages, so this is a good principle to know.

    To get a separate copy of the variables in the loop, call another function that returns the delegate you want to store, passing the loop variable to it.

    import std.stdio;
    alias Clojure = void delegate();
    Clojure[] clojures;
    
    void main(){
        foreach(a; ["Je", "Tu", "En", "Oui", "Na"])
            clojures ~= ((b) => { write(b);})(a);
        foreach(c; clojures)
            c();
        writeln(" batman");
    }
    

    The clojures ~= ((b) => { write(b);})(a); line has changed: that defines a quick delegate that returns the delegate. The extra function-returning-function closes over a snapshot of the loop state, instead of just the function-level local variables.

    I use this a lot in JavaScript too:

     function makeHandler(item) {
          return function() {
             // use item here
          };
     }
    
    var array = [1,2,3];
    for(var I = 0; I < array.length; I++)
       foo.addEventListener("click", makeHandler(array[I]));
    

    This is the same thing done for the same reason as the D, just in different syntax, and broken up into bigger functions instead of trying to do it as a one-liner.

    We define a function which returns a function that uses the captured loop variable. At the usage point, we call the one function which returns the delegate that is stored for later.

    In the shorthand D syntax, ((b) => { write(b);})(a);, the (b) => ... is the makeHandler function seen in the javascript. The { write(b); } it returns is shorthand for return function() { ... } in JS (BTW that same JS syntax basically works ion D too, you can write a longhand thing with the delegate or function keywords. D's function doesn't capture variables though, delegate does that..)

    Then, finally, the parenthesis around it and the (a) at the end is just to call the function. The stuff inside is the same as makeHandler, the (...)(a) calls it; it is makeHadndler(a).