diff --git a/CommonMistakes.md b/CommonMistakes.md index 482561bc..2d94b889 100644 --- a/CommonMistakes.md +++ b/CommonMistakes.md @@ -2,12 +2,72 @@ Table of Contents ================= + [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) # 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. +# 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 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: