17 comments

[ 2.2 ms ] story [ 48.5 ms ] thread
C# made that change in C# 5 (2012) (https://ericlippert.com/2009/11/12/closing-over-the-loop-var...)

Swift had the problem in C-style for loops but not in “for…in” loops (https://www.timekl.com/blog/2015/11/26/variable-capture-in-g...), but got rid of it when it removed C-style for loops altogether in Swift 3 (September 2016) (https://github.com/apple/swift-evolution/blob/main/proposals...)

I was shocked that C# would make this change--which is specifically to modify the scoping semantics of a C-style for loop to match those of a for-in loop, not to merely change the semantic of for-in loops; the latter seems entirely reasonable... while the former is just ridiculous to me (and OMG I am so glad I don't code in Go ;P)--but, the reference you cited for that in fact states the exact opposite: that they would never do such a strange thing (and, thereby, agrees with the person on reddit who made this post and who is adamantly against this change).

> We have this same problem in for blocks, but for blocks are much looser about what “the loop variable” is; there can be more than one[1. or none at all.] variable declared in the for loop header, it can be incremented in odd ways, and it seems implausible that people would consider each iteration of the for loop to contain a fresh crop of variables. When you say for(int i; i < 10; i += 1) { ... } it seems dead obvious that the i += 1 means “increment the loop variable” and that there is one loop variable for the whole loop, not a new fresh variable i every time through!

> We certainly would not make this proposed change apply to for loops.

OP misunderstood but you have it right. C# did not change the behavior of "for" loops; they only changed "foreach" loops. As an example:

    List<Action> list = new();
    for (var i = 0; i < 10; i++) 
        list.Add(() => Console.WriteLine(i));
    foreach (var action in list)
        action();
This (still) prints all 10s. All of the closures are referencing the same "i".
I don't misunderstand it. I know the facts in C#. But the Go proposal is different. The proposed change is for both "for;;" loops and "for-range" loops.
I was referring to the HN poster "Someone" at the beginning of this subthread, my apologies for the confusion--I didn't want to name them explicitly in my post since that feels a little aggressive. They said C# 5 made this change, which is wrong; that's why I explained with a C# snippet. This subthread isn't referring to your post at all.
Please note that, the Go proposal is for both "for-range" loops and traditional "for;;" c-like loops.

I think it is okay to make the change for "for-range" loops, but not okay for traditional "for;;" c-like loops.

I'm not persuaded the issue as given by the Reddit OP is worth the lack of progress.
Can someone ELI12 what's happening here? I haven't had the pleasure of learning Go yet.
Go is changing the semantics of its for loops and the user is not a fan of this - at least in the subtleties. Most people, from what I can tell, are very much for this change. And note, Go is introducing quite sophisticated backwards compatibility features to avoid changing semantics for programs that are need the old behaviour.
Not exactly. I'm fan of the change for "for-range" loops, but not a fan for the change for "for;;" loops.
Go loops define pointers for scope-defined variables at the loop, rather than iteration, level, so there's a common "gotcha" where you intern a pointer to access it outside of the loop (such as pushing it onto an array), and then discover that its value is clobbered by subsequent runs of the loop. There is a proposal to change this, since it's a source of confusion and bugs, particularly when `range`ing over pointers.

What the code snippet demonstrates is that, for a contrived scenario, the proposal in question also creates an unexpected result when you expect pointers to remain stable between loop iterations. To be honest, I can't think of any time where this would be an issue for me (as opposed to writing `foo := foo`); Go has quite a bit of cognitive friction in its loop semantics, so anything to reduce unexpected behavior is welcome to my mind.

In short, the new proposed Go semantics automatically declared an inside variable which has the same name as the iteration variable "I". So the inside "I" is not the iteration variable "I".
All loops run the risk of never exiting; it's basically how loops work.

This construct seems highly artificial. Perhaps it's an issue in some other less artificial cases, but the change does solve a high number of common errors, so overall it seems like a net win. I don't really see the problem here.

The change for "for-range" loops generally has positive effects (except that it will hurt performance sometimes).

But the change for "for;;" loops has many bad effects.

Is the code, as shown in the HN headline, correct? An exclamation mark seems to have gone missing, compared to the Reddit version.
It should be p != &I as the condition.
Sorry, it looks the HN backend edited the title automatically. The condition should be "p != &I". (BTW, the HN backend also auto changed all "i" to "I").