Common mistakes: Add "using reference to loop iterator variable"

Eyal Posener 2019-12-21 10:54:07 +02:00
parent ca3708fee9
commit e6ad5632aa

@ -2,12 +2,72 @@ Table of Contents
================= =================
+ [Introduction](#introduction) + [Introduction](#introduction)
+ [Using reference to loop iterator variable](#using-reference-to-loop-iterator-variable)
+ [Using goroutines on loop iterator variables](#using-goroutines-on-loop-iterator-variables) + [Using goroutines on loop iterator variables](#using-goroutines-on-loop-iterator-variables)
# Introduction # Introduction
When new programmers start using Go or when old Go programmers start using a new concept, there are some common mistakes that many of them make. Here is a non-exhaustive list of some frequent mistakes that show up on the mailing lists and in IRC. When new programmers start using Go or when old Go programmers start using a new concept, there are some common mistakes that many of them make. Here is a non-exhaustive list of some frequent mistakes that show up on the mailing lists and in IRC.
# Using reference to loop iterator variable
In Go, the loop iterator variable is a single variable that takes different values in each loop iteration. This is very efficient, but might lead to unintended behavior when used incorrectly. For example, see the following program:
```go
func main() {
var out []*int
for i := 0; i < 3; i++ {
out = append(out, &i)
}
fmt.Println("Values:", *out[0], *out[1], *out[2])
fmt.Println("Addresses:", out[0], out[1], out[2])
}
```
It will output unexpected results:
```
Values: 3 3 3
Addresses: 0x40e020 0x40e020 0x40e020
```
Explanation: in each iteration we append the address of `i` to the `out` slice, but since it is the same variable, we append the same address which eventually contains the last value that was assigned to `i`. One of the solutions is to copy the loop variable into a new variable:
```diff
for i := 0; i < 3; i++ {
+ i := i // Copy i into a new variable.
out = append(out, &i)
}
```
The new output of the program is what was expected:
```
Values: 0 1 2
Addresses: 0x40e020 0x40e024 0x40e028
```
Explanation: the line `i := i` copies the loop variable `i` into a new variable scoped to the for loop body block, also called `i`. The address of the new variable is the one that is appended to the array, which makes it outlive the for loop body block. In each loop iteration a new variable is created.
While this example might look a bit obvious, the same unexpected behavior could be more hidden in some other cases. For example, the loop variable can be an array and the reference can be a slice:
```go
func main() {
var out [][]int
for _, i := range [][1]int{{1}, {2}, {3}} {
out = append(out, i[:])
}
fmt.Println("Values:", out)
}
```
Output:
```
Values: [[3] [3] [3]]
```
The same issue can be demonstrated also when the loop variable is being used in a Goroutine (see the following section).
# Using goroutines on loop iterator variables # Using goroutines on loop iterator variables
When iterating in Go, one might attempt to use goroutines to process data in parallel. For example, you might write something like this, using a closure: When iterating in Go, one might attempt to use goroutines to process data in parallel. For example, you might write something like this, using a closure: