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