SoFunction
Updated on 2025-03-05

Analysis of the pits that most Go programmers have gone through

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 outputabcThese three characters (maybe different in sequence), but what is actually possible isccc. 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 likelyccc, because for eachCharThe 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 outputcbaThis output is no longercccNow. This feature will be enabled by default in Go 1.22 and does not require settingsGOEXPERIMENTNow. 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-clauseThe 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-rangeStatement,3-clauseThe 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, soidsThe 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-clauseThe 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!