What is the most common syntax error you make in Go? Many people may have different answers, but one of the most common answers is the use of variables in for loops. Even Go team developers, I have seen their commits make this mistake, not to mention other Go developers, like this problem at Let’s Encrypt, almost every Go developer has made this mistake, and this type of error has left a shadow on my heart, every time I write a for loop, I often use local variables to shade the loop variable, even if there is no problem.

Russ Cox checked 14,000 go modules and about 12,000 github repositories, searching for tricks like using x := x to solve the loop variable problem, and found that about 600 commits were solving the problem, and upon closer inspection, about half of them were unnecessary.

For example, in the following two projects, one is necessary and one 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 is not necessary. The other is a struct type, and the method Receiver is a pointer type, so it needs to be modified so that each loop is a different pointer.

So you see almost the same code, some are bugs and some are not bugs, don’t you hate it?

The circular variable problem has always been a problem and is not yet easy to detect; it exists in multiple 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 example at the top.

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)
}

It is impossible to prevent. The most important reason is that the current loop variables i, v, item, etc. are per-loop, not per-iteration. This means that these loop variables are unique in this loop, not per-iteration.

The best trick to solve this problem is to use local variables (x := x), such as:

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

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

Although the Go team has ignored this issue for many years, just the other day Russ Cox finally stepped in and created a discussion thread: #56010.

Because changing this syntax and changing the semantics of loop variables from per-loop to per-iteration breaks the promise of backwards compatibility, this change is still cautious and is still under discussion, but it is clearly a feature that Gopher expects to change.

And Russ Cox has also come up with a solution, if the 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, then use the old per-loop compiler, and if it is larger than this version, use per-iteration.