Loop variables
Speaking of the mistake that every programmer must make, it must be the mistake of "loop variable". Even Go developers have made this mistake, which is also mentioned in Go's FAQ.
What happens with closures running as goroutines?[1]:
func main() { var wg values := []string{"a", "b", "c"} for _, v := range values { (1) go func() { (v) () }() } () }
You may expect to outputa
、b
、c
These three characters (maybe different in sequence), but what is actually possible isc
、c
、c
. This is because the scope of the loop variable is the entire loop, not a single iteration, so the variable used in the loop body is the same variable, rather than a new variable for each iteration.
This error is sometimes hidden very deeply, and even without goroutine, it is possible. For example, the following code does not use additional goroutines and closures, which is also problematic:
package main import ( "fmt" ) type Char struct { Char *string } func main() { var chars []Char values := []string{"a", "b", "c"} for _, v := range values { chars = append(chars, Char{Char: &v}) } for _, v := range chars { (*) } }
The output is also likelyc
、c
、c
, because for eachChar
The field assigned a pointer to v, v is a variable throughout the loop, so the final result isc
。
The Go team also realized this issue very early, but considering the compatibility issue and the tolerance level of everyone, that's it. Every Go programmer falls here and has a memory, so this design has not been changed. I fell a lot here, so I was so nervous when writing for loops. Just like the online processing of Russ Cox statistics, whether it is necessary or not, many times I first assign the loop variable to a local variable and then use it, such as the following code:
for _, v := range values { v := v (1) go func() { (v) () }() }
Variables are only used in loop bodies
In May this year, Russ Cox couldn't help but propose a proposal #60078[2]. The content of the proposal is in the for loop. If the variable is only used in the loop body, a new variable will be created in each iteration instead of using the same variable. This proposal has attracted a lot of attention, and many people are discussing this proposal. This proposal has been accepted. The specific content of the proposal is in the document Proposal: Less Error-Prone Loop Variable Scoping[3].
If you use Go 1.21, you can start this feature and useGOEXPERIMENT=loopvar go run
Run the above program and the outputc
、b
、a
This output is no longerc
、c
、c
Now. This feature will be enabled by default in Go 1.22 and does not require settingsGOEXPERIMENT
Now. It will take a month or two to officially release go 1.22,
You can use getip to test:
$ gotip run a b c
Not justfor-range
,The following3-clause
The same problem:
func main() { var ids []*int for i := 0; i < 3; i++ { i = 10 } for _, id := range ids { (*id) } }
This issue will also be fixed in Go 1.22.
Compare C# language
C# language is only modifiedfor-range
Statement,3-clause
The statement has not been modified, and both Go have been modified.
but, The problem is here. Like the following code, will Go 1.22 be the same as the previous code?
func main() { var ids []*int for i := 0; i < 3; i++ { i = 10 ids = append(ids, &i) } for _, id := range ids { (*id) } }
If you use Go 1.21, it will output11
. If you use Go 1.22, it will output10
. The reason is that after this proposal is implemented, a new variable will be created every time iteration, soids
The elements in it all point to different variables, not the same variable.
It seems to break the promise of backward compatibility. If you wanted to use this corner case before, Go1.22 is no longer compatible.
Going further, you will find that you will execute again3-clause
The third clause of the variable has been recreated, such as the following code:
func main() { for i, p := 0, (*int)(nil); i < 3; println("3rd-clause:", &i, p) { p = &i ("loop body:", &i, p) i++ } }
Output:
$gotip run
loop body: 0x14000120018 0x14000120018
3rd-clause: 0x14000120030 0x14000120018 // &i has become 0x14000120030
loop body: 0x14000120030 0x14000120030
3rd-clause: 0x14000120038 0x14000120030 // &i has become 0x14000120038
loop body: 0x14000120038 0x14000120038
3rd-clause: 0x14000120040 0x14000120038 // &i has become 0x14000120040
References
/golang/go/issues/60078
/doc/faq#closures_and_goroutines
The above is the detailed content of the pits that most Go programmers have walked through. For more information about the pits that Go programmers have walked through, please pay attention to my other related articles!