How to avoid variable capturing in `foreach` loop with lambdas?

tsbockman thomas.bockman at gmail.com
Thu Jan 5 13:43:32 UTC 2023


On Thursday, 5 January 2023 at 11:55:33 UTC, thebluepandabear 
wrote:
> ```D
> foreach (BoardSize boardSize; arr) {
>     Button button = new Button();
>     button.text = format("%sx%s", boardSize[0], boardSize[1]);
>     button.onButtonClick = {
>         
> eventHandler.settingsWindow_onBoardSizeButtonClick(boardSize);
>     };
>     button.onButtonClick();
>     _boardSizeRow.addChild(button);
> }
> ```
>
> Running this code, I had expected that everything would work 
> fine. Unfortunately upon running the code, tapping each of the 
> buttons returned only the largest `boardSize` value, the one 
> which is gets iterated last.

The problem is twofold:

1. Closures in D capture their environment by reference.
2. D (incorrectly, in my opinion) considers loop-local variables 
to have the same identity across each iteration of the loop 
within a single function call.

So, `boardSize` in your event handler is a reference to a single 
variable whose value is overwritten on each iteration of the 
loop. As the event handlers are (I presume) never called until 
after the loop has terminated, the only value they will ever see 
is whichever was set by the final iteration of the loop in that 
function call.

There are at least two possible solutions:

1. Use a `struct` to explicitly capture `boardSize` by value, 
instead of by reference:
```D
static struct ClickHandler {
     // If eventHandler is not a global variable of some sort, add 
another field for it:
     BoardSize iteration_boardSize;
     this(BoardSize iteration_boardSize) {
         this.iteration_boardSize = iteration_boardSize;
     }

     void opCall() {
         
eventHandler.settingsWindow_onBoardSizeButtonClick(iteration_boardSize);
     }
}

foreach (BoardSize loop_boardSize; arr) {
     Button button = new Button();
     button.text = format("%sx%s", loop_boardSize[0], 
loop_boardSize[1]);
     button.onButtonClick = &(new 
ClickHandler(loop_boardSize)).opCall;
     button.onButtonClick();
     _boardSizeRow.addChild(button);
}
```

2. Use a nested function call with a `boardSize` parameter to 
create a copy of `boardSize`  with a unique identity on each 
iteration of the loop:
```D
foreach (BoardSize loop_boardSize; arr) {
     Button button = new Button();
     button.text = format("%sx%s", loop_boardSize[0], 
loop_boardSize[1]);
     button.onButtonClick = (BoardSize iteration_boardSize) {
         return {
             
eventHandler.settingsWindow_onBoardSizeButtonClick(iteration_boardSize);
         };
     }(loop_boardSize);
     button.onButtonClick();
     _boardSizeRow.addChild(button);
}
```

These two solutions should compile to approximately the same 
runtime code, with optimizations enabled. So, it's really down to 
personal preference; the former is more explicit about what the 
computer is to do, while the latter is more concise.


More information about the Digitalmars-d-learn mailing list