Go common pitfall #1: slice appending
Go's slice is simple to use, but there are some easy to make mistakes if you are not aware of its internals.
For example, with following program:
package main
import "fmt"
func main() {
healthyFoods := []string{"orange", "apple", "lettuce"}
myFoods := healthyFoods[0:2]
myFoods = append(myFoods, "bacon")
fmt.Println(healthyFoods)
}
You might think that it would print [orange apple lettuce]
, but the actual output is: [orange apple bacon]
. It is as dangerous as you can see, bacon is definitely not healthy!
Understand the problem
To understand why it happens, it is recommended to read this excellent blog post: Go Slices: usage and internals.
The healthyFoods
slice is created with:
- length: 3
- capacity: 3
It is backed by an array with length equals to 3.
The myFoods
slice is created with:
- length: 2
- capacity: 3 (the capacity of the backing array)
An important thing to keep it mind is, healthyFoods
and myFoods
backed by the same array.
This is what happened when appending "bacon"
to myFoods
slice:
- Go sees that
myFoods
hasn't used all of its capacity (2 < 3) "bacon"
is written to the slot third slot in the backing array, which"lettuce"
is currently located
That is the reason healthyFoods
changed, even though we never directly did anything with it.
How to fix it?
Understanding the problem, the easiest way to fix it is explicitly set the capacity when slicing a slice.
myFoods := healthyFoods[0:2:2]
There we go, our program is now printing the expected [orange apple lettuce]
output. This form of slicing is called full slice expressions. IMO, this feature should be used more often that it should.
What happened when appending "bacon"
to myFoods
now:
- Go sees that
myFoods
has used up all of its capacity (2 = 2) - Go creates another backing array and copy
myFoods
's elements over
Therefore, the original healthyFoods
is left alone untouched.