After more than ten years, the most fallible Go syntax is finally about to change

Original link: https://colobu.com/2022/10/04/redefining-for-loop-variable-semantics/

What is your most error-prone syntax in Go? Many people may have different answers, but one of the most common is the use of variables in for loops. Even developers on the Go team, I’ve seen code they commit make this kind of mistake, not to mention other Go developers, like this problem at Let’s Encrypt , which is made by almost every Go developer Error, this type of error has left a shadow in my mind. Every time I write a for loop, I feel nervous. I often use local variables to shade the loop variables, even if there is no problem.

Russ Cox checked 14,000 go modules, about 12,000 github repositories, searched for the use of x := x to solve the problem of loop variables, and found that about 600 commits solved this problem. After careful observation, About half of the commits are unnecessary, maybe due to inaccurate static tool analysis or confusion about semantics, or a cautious attitude like I was bitten by snakes for ten years. For example, in the following two projects, one is necessary and the other is not:


1
2
3
4

for _, informer := range c. informerMap {
+ informer := informer
go informer.Run(stopCh)
}

another:


1
2
3
4

for _, a := range alarms {
+ a := a
go a.Monitor(b)
}

One of the loop variables is an interface, so it’s not necessary. The other is a struct type, and the method’s Receiver is a pointer type, so it needs to be modified so that each loop is a different pointer.

So you look at almost the same code, some are bugs and some are not bugs, do you hate it?

The loop variable problem has always been a problem, and it is not easy to find, it exists in many types, such as:


1
2
3
4

var all []*Item
for _, item := range items {
all = append (all, &item)
}

or:


1
2
3
4
5
6
7

var prints[] func ()
for _, v := range [] int {1 , 2 , 3 } {
prints = append (prints, func () { fmt.Println(v) })
}
for _, print := range prints {
print ()
}

Or like the top example:


1
2
3

for _, a := range alarms {
go a.Monitor(b)
}

or:


1
2
3
4
5

for _, a := range alarms {
go func () {
fmt.Println(a)
}
}

or:


1
2
3
4

for _, scheme := range artifact.Schemes {
Runtime.artifactByScheme[scheme.ID] = id
Runtime.schemesByID[scheme.ID] = scheme
}

or:


1
2
3

for i := 0 ; i < n; i++ {
use(&i)
}

Hard to guard against. The most important reason is that the current spirit ring variables i , v , item , etc. are per-loop , not per-iteration . That is, these loop variables are unique within the loop, not per iteration.
The best trick to solve this problem is to use local variables ( x := x ) to hack, like:


1
2
3
4

for _, a := range alarms {
a := a
go a.Monitor(b)
}

People are suffering from it, so there are some proposals to improve this syntax. For example #20733 , #24282 , #21130 .

Although the Go team ignored this issue for many years, today, just today, Russ Cox finally got his hands on it, and he created a discussion thread: #56010 .

Because this syntax is changed, the semantics of loop variables are changed from per-loop to per-iteration , which destroys the promise of backward compatibility, so this change is still relatively cautious and is still under discussion, but obviously, this is the expectation of the majority of Gophers A modified feature.

And Russ Cox also thought of a solution, if this feature is added in a certain version, such as 1.30 , then if the version defined in the go module is smaller than this version of the library, use the old per-loop compilation, greater than or equal to this version Use per-iteration .

You can follow this discussion, or give a like, hoping this feature will be added to the trunk soon.

This article is reprinted from: https://colobu.com/2022/10/04/redefining-for-loop-variable-semantics/
This site is for inclusion only, and the copyright belongs to the original author.