Thread overview
How can I put the current value of a variable into a delegate?
May 06

Delegates can be a pain, as they often have results different from what one would intuitively expect. This can easily result in bugs.

Here's a line that caused a bug that took me awhile to find:

foreach(card; unitCards) card.submitted = delegate() => selectUnit(card.unit);

Each UnitInfoCard object (which card is a member of) contains a Unit object called unit. The intention of this line was that each object in unitCards would call selectUnit with it's own unit every time it calls submitted. Instead, every card calls submitted with the last value of card.

This is because the delegate assignment causes the local card variable to remain alive. The delegate that's assigned is linked to this variable itself, not the value at the time that the delegate is assigned.

Is there a way I can dereference a variable when placing it in a delegate, so that it's current value is used, rather than the variable itself?

May 06

On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:

>

This is because the delegate assignment causes the local card variable to remain alive. The delegate that's assigned is linked to this variable itself, not the value at the time that the delegate is assigned.

This is https://issues.dlang.org/show_bug.cgi?id=23136. Perhaps it can be fixed in the next edition.

>

Is there a way I can dereference a variable when placing it in a delegate, so that it's current value is used, rather than the variable itself?

I think you would need to make an array before the loop, assign to an indexed element and use that in the delegate.

May 06

On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:

>

Here's a line that caused a bug that took me awhile to find:

foreach(card; unitCards) card.submitted = delegate() => selectUnit(card.unit);

I think you can do:

    import std.algorithm.iteration : each;
    unitCards.each!(c => c.submitted = () => selectUnit(c.unit));
May 06

On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:

>

Delegates can be a pain, as they often have results different from what one would intuitively expect. This can easily result in bugs.

Here's a line that caused a bug that took me awhile to find:

foreach(card; unitCards) card.submitted = delegate() => selectUnit(card.unit);

Each UnitInfoCard object (which card is a member of) contains a Unit object called unit. The intention of this line was that each object in unitCards would call selectUnit with it's own unit every time it calls submitted. Instead, every card calls submitted with the last value of card.

Yes, this is because the foreach loop reuses the same memory slot for card.

Even though this is allocated as a closure, it still only allocates the frame stack of the enclosing function, and does not allocate a new slot for each loop iteration.

You can force this by using a lambda which allocates the closure:

foreach(card; unitCards)
    card.submitted = (c2) { return () => selectUnit(c2.unit); }(card);

This is a lambda which accepts card as a parameter, and returns an appropriate delegate. It is important to use a parameter, because if you just use card inside there, it's still using the single stack frame of the calling function!

I renamed the inner parameter c2 to avoid confusion, but you could name it card also. Essentially, the stack frame of the inner function is now allocated a closure, and it has it's own reference to card as a parameter.

This is a very old issue: https://issues.dlang.org/show_bug.cgi?id=2043 since "moved" to https://issues.dlang.org/show_bug.cgi?id=23136

I would love to see a solution, but the workaround at least exists!

-Steve

May 08

On Monday, 6 May 2024 at 16:41:38 UTC, Steven Schveighoffer wrote:

>

This is a very old issue: https://issues.dlang.org/show_bug.cgi?id=2043 since "moved" to https://issues.dlang.org/show_bug.cgi?id=23136

I would love to see a solution, but the workaround at least exists!

-Steve

Interestingly enough C# used to have the same behaviour but MS decided to go for a breaking change in C# 5; now it behaves as most people expect.

Since it's an unsolved problem to keep links working for 10+ years I gave up looking for something official about the subject. Here's an SO question about it though:

https://stackoverflow.com/questions/14184515/action-delegate-uses-the-last-values-of-variables-declared-outside-foreach-loop

May 08

On Monday, 6 May 2024 at 16:41:38 UTC, Steven Schveighoffer wrote:

>

On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:

>

Delegates can be a pain, as they often have results different from what one would intuitively expect. This can easily result in bugs.

Here's a line that caused a bug that took me awhile to find:

foreach(card; unitCards) card.submitted = delegate() => selectUnit(card.unit);

Each UnitInfoCard object (which card is a member of) contains a Unit object called unit. The intention of this line was that each object in unitCards would call selectUnit with it's own unit every time it calls submitted. Instead, every card calls submitted with the last value of card.

Yes, this is because the foreach loop reuses the same memory slot for card.

Even though this is allocated as a closure, it still only allocates the frame stack of the enclosing function, and does not allocate a new slot for each loop iteration.

You can force this by using a lambda which allocates the closure:

foreach(card; unitCards)
    card.submitted = (c2) { return () => selectUnit(c2.unit); }(card);

This is a lambda which accepts card as a parameter, and returns an appropriate delegate. It is important to use a parameter, because if you just use card inside there, it's still using the single stack frame of the calling function!

...

I would love to see a solution, but the workaround at least exists!

-Steve

Well that's something. It's not a very good solution for a language that aims for readability. It took me awhile looking at it to figure out what it is about, as I'm not familiar with this syntax.

The solution that I did before seeing this was to add a function to UnitInfoCard to give it a delegate with a Unit unit parameter, and then that function would give that function with the unit parameter set to itself to it's own submitted member. I will probably keep it like this for readability.

    void clickAction(void delegate(Unit) @safe clickAction) {
        submitted = () => clickAction(unit);
    }
May 09

On Wednesday, 8 May 2024 at 12:29:05 UTC, Rene Zwanenburg wrote:

>

Interestingly enough C# used to have the same behaviour but MS decided to go for a breaking change in C# 5; now it behaves as most people expect.

Wow! I wonder if D would be willing to allow such a breaking change with the release of Phobos 3. My choice would be to have it use the current value by default for value types, but allow them to be linked to the same memory address using *& when the variable is placed in a delegate. I think that the distinction between value types and reference types should be consistent.

If such a breaking change isn't considered acceptable, I suppose a new operator can be introduced for dereferencing a variable when placed in a delegate. Maybe # or $ if they don't conflict with any existing use of those symbols.