Jump to page: 1 2
Thread overview
D isn't the only language with janky closure semantics
Aug 30
ltdk
Aug 30
user1234
Aug 30
Dom DiSc
Aug 30
monkyyy
Aug 30
monkyyy
6 days ago
Paul Backus
Aug 30
JN
August 29
I happen to be working on some JS today (gasp!), and ran into this odd
behaviour.  Code snippet:

```js
function buildDialog(label, buttons) {
	const dlg = document.createElementNS(...);
	...
	for (button of buttons) {
		const btnElem = document.createElementNS(...);
		...
		btnElem.addEventListener(click, (ev) => {
			button.action(ev); // <--- this was acting up
		});
	}
}

buildDialog("My dialog", [
	{
		label: "Yes",
		action: (ev) => {
			alert("Yes");
		}
	},
	{
		label: "No",
		action: (ev) => {
			alert("No");
		}
	}
]);
```

Running the above dialog, it seems that clicking on either button calls the "No" callback.  For some reason, the "Yes" callback never gets called.  This left be scratching my head for a while, until I thought of the similar situation in D:

```d
	struct Button {
		...
		void delegate(Event) action;
	}

	void delegate(Event)[] actions;
	foreach (button; buttons) {
		actions ~= (event) => button.action(event);
	}
```

As anyone who's run into a similar issue would immediately notice, the delegate here closes on the loop variable `button`, which unfortunately shares the same stack location across loop iterations, so that the call to `button.action` doesn't call the action of the *current* instance of Button, but the one that gets assigned to the last loop iteration.

Suspecting a similar issue in the JS code, I modified the addEventListener call like this:

```js
	const action = button.action;
	btnElem.addEventListener(click, (ev) => {
		action(ev);
	});
```

and voila!  Now the code works!  So apparently, copying button.action into a local variable makes the lambda close properly over an isolated instance of button.action rather than closing over the shared loop variable `button`, which gets overwritten by each subsequent loop iteration.

//

Now, JS is certainly not a model language to follow, but it's interesting how it shares with D the same issue over closures involving loop variables.

I guess that means we're not alone. :-P  Even though this situation is definitely not ideal.


T

-- 
Don't throw out the baby with the bathwater. Use your hands...
August 29

On Friday, 29 August 2025 at 22:28:17 UTC, H. S. Teoh wrote:

>

Now, JS is certainly not a model language to follow, but it's interesting how it shares with D the same issue over closures involving loop variables.

I guess that means we're not alone. :-P Even though this situation is definitely not ideal.

Oh yeah, JS has similar behavior. You get the desired behavior by using let at the variable declaration. Otherwise, it becomes a regular declaration (one instance per function).

I think your original code will work as you want with:

for (let button of buttons) {

Would be nice to have a similar trick for D...

-Steve

August 30
On Friday, 29 August 2025 at 22:28:17 UTC, H. S. Teoh wrote:

>
> Now, JS is certainly not a model language to follow, but it's interesting how it shares with D the same issue over closures involving loop variables.
>
> I guess that means we're not alone. :-P  Even though this situation is definitely not ideal.
>
>
> T

Pior to 1.22, Golang also behaves similarly. They later "fixed" the issue: https://go.dev/blog/loopvar-preview .

C# also moved away from Dlang's behavior since C# 5, 1 member of C# team commented about that change: https://github.com/golang/go/discussions/56010#discussioncomment-3788526
August 30
On Friday, 29 August 2025 at 22:28:17 UTC, H. S. Teoh wrote:
> I happen to be working on some JS today (gasp!), and ran into this odd
> behaviour.
>
> [...]
>
> T

This https://github.com/dlang/dmd/issues/18108, right ?

August 30
On Saturday, 30 August 2025 at 11:40:38 UTC, user1234 wrote:
> On Friday, 29 August 2025 at 22:28:17 UTC, H. S. Teoh wrote:
>> I happen to be working on some JS today (gasp!), and ran into this odd
>> behaviour.
>>
>> [...]
>>
>> T
>
> This https://github.com/dlang/dmd/issues/18108, right ?

duplicate of https://github.com/dlang/dmd/issues/19929 - so it's discussed since years. :-(
August 30
On 8/30/25 12:18, ltdk wrote:
> On Friday, 29 August 2025 at 22:28:17 UTC, H. S. Teoh wrote:
> 
>>
>> Now, JS is certainly not a model language to follow, but it's interesting how it shares with D the same issue over closures involving loop variables.
>>
>> I guess that means we're not alone. :-P  Even though this situation is definitely not ideal.
>>
>>
>> T
> 
> Pior to 1.22, Golang also behaves similarly. They later "fixed" the issue: https://go.dev/blog/loopvar-preview .
> 
> C# also moved away from Dlang's behavior since C# 5, 1 member of C# team commented about that change: https://github.com/golang/go/ discussions/56010#discussioncomment-3788526

These are _not_ "Dlang's behavior". They did not corrupt memory, D does.

D closures are much more broken than this simple usability problem.

What you are talking about is essentially the difference between a lowering:

```
foreach(i;0..n){ ... } -> for(int i=0;i<n;i++){ ... }
```
and
```
foreach(i;0..n){ ...} -> for(int _i=0;_i<n;_i++){ int i=_i; ... }
```
The other languages basically moved from the first variant to the second one.

It's a somewhat subtle point because there are many simple cases where using the first lowering behaves similarly to using the second lowering in D.

D is just broken here, it actually already uses the second lowering and it still does not work properly.

Of course, this should be fixed, because it has the same usability problem that the other languages fixed and is even unsound, causing more usability problems on top of that.
August 30
On Sat, Aug 30, 2025 at 12:08:15PM +0000, Dom DiSc via Digitalmars-d wrote:
> On Saturday, 30 August 2025 at 11:40:38 UTC, user1234 wrote:
> > On Friday, 29 August 2025 at 22:28:17 UTC, H. S. Teoh wrote:
> > > I happen to be working on some JS today (gasp!), and ran into this
> > > odd behaviour.
[...]
> > This https://github.com/dlang/dmd/issues/18108, right ?
> 
> duplicate of https://github.com/dlang/dmd/issues/19929 - so it's discussed since years. :-(

I'm well aware of this issue in D, and of how long it's been around.  It was just surprising that JS exhibits the same behaviour.


T

-- 
An imaginary friend squared is a real enemy.
August 30
On Friday, 29 August 2025 at 22:28:17 UTC, H. S. Teoh wrote:
>

Why are delegates in D an aggregate of a context pointer and a function pointer, not like C++'s lambdas (anonymous class with `operator()`)?
August 30
On Sat, Aug 30, 2025 at 05:20:18PM +0000, Konstantin via Digitalmars-d wrote:
> Why are delegates in D an aggregate of a context pointer and a
> function pointer, not like C++'s lambdas (anonymous class with
> `operator()`)?

That's just an implementation issue.  I think the original intention is to unify them with class member function pointers, or something like that.


T

-- 
I am not superstitious; I'm just a little stitious.
August 30
On Saturday, 30 August 2025 at 18:43:49 UTC, H. S. Teoh wrote:
> On Sat, Aug 30, 2025 at 05:20:18PM +0000, Konstantin via Digitalmars-d wrote:
>> Why are delegates in D an aggregate of a context pointer and a
>> function pointer, not like C++'s lambdas (anonymous class with
>> `operator()`)?
>
> That's just an implementation issue.  I think the original intention is to unify them with class member function pointers, or something like that.
>
>
> T

Is it not a #wontfix?
« First   ‹ Prev
1 2