Common Gotchas in Go
First thing is first. Happy New Years 🎉🎉
Now that’s out of the way, let’s talk about Go. I recently finished making my first real Go program. It’s called “Fix All Conflicts” or fac for short. It’s an easy-to-use console user interface for fixing git merge conflicts. I made it because I never found a merge tool that was intuitive enough to use.
The process was quite fun and I learned a lot about Go in the process. So, to wrap up my first official foray into Rob Pike’s mystical land of gophers, I decided to write down some of the common “Gotchas!” that any beginning Gopher - me - can run into.
Gophers can be quite aggressive sometimes.
⚠️ 1.) Range
The range
function is one of the most commonly used functions in Go. Here’s a sample use case of the range
function. Note that for some demented reason, we decided to make all the animals in the zoo have 999
legs.
package main
import "fmt"
type Animal struct {
name string
legs int
}
func main() {
zoo := []Animal{
Animal{"Dog", 4},
Animal{"Chicken", 2},
Animal{"Snail", 0},
}
fmt.Printf("-> Before update %v\n", zoo)
for _, animal := range zoo {
// 🚨 Oppps! `animal` is a copy of an element 😧
animal.legs = 999
}
fmt.Printf("\n-> After update %v\n", zoo)
}
The above code looks innocent enough. However, you may be surprised to find that animals.legs = 999
didn’t do anything.
-> Before update [{Dog 4} {Chicken 2} {Snail 0}]
-> After update [{Dog 4} {Chicken 2} {Snail 0}] 🚨🚨🚨
Lesson
Value property of
range
(stored here asanimal
) is a copy of the value fromzoo
, not a pointer to the value inzoo
.
The Fix
In order to modify an element within the array, we must change the element via its pointer.
for idx := range zoo {
zoo[idx].legs = 999
}
This may look quite trivial but you may be surprised to find this as a one of the most common source of bugs; at least for me!
» Go playground #1 for you to play around in
⚠️ 2.) The … thingy
You may have used the …
keyword in the C programming language to create a variadic function; variadic function is a function that takes a variable number or type of arguments.
In C, you have to successively call the va_arg
macro in order to access the optional arguments. And if you use the variadic argument in any other way, the compiler will throw an error.
int add_em_up (int count,...) {
...
va_start (ap, count); /* Initialize the argument list */
for (i = 0; i < count; i++)
sum += va_arg(ap, int); /* Get the next argument value */
va_end (ap); /* Clean up */
return sum
}
In Go however, things are similar but quite different at the same time. Here is a variadic function myFprint
in Go. Notice how the variadic argument a
is being used.
func myFprint(format string, a ...interface{}) {
if len(a) == 0 {
fmt.Printf(format)
} else {
// ⚠️ `a` should be `a...`
fmt.Printf(format, a)
// ✅
fmt.Printf(format, a...)
}
}
func main() {
myFprint("%s : line %d\n", "file.txt", 49)
}
[file.txt %!s(int=49)] : line %!d(MISSING)
file.txt : line 49
You’d think that the compiler would throw an error here for using the variadic parameter a
in a wrong way. But notice how fmt.Sprintf
just used the first argument in a
without throwing a fit.
Lesson
In Go, variadic parameters are converted to slices by the compiler
This means that the variadic argument a
is in fact, just a slice. Because of this, the code below is completely valid.
// `a` is just a slice!
for _, elem := range a {
fmt.Println(elem)
}
The Fix
Remember to type ALL THREE DOTS whenever using variadic parameters!
» Go playground #2 for you to play around in
⚠️ 3.) Slicing
If you have done your fair share of slicing in Python, you may remember that slicing in Python gives you a new list with just the references to the elements copied over. This property allows for code like this in Python.
a = [1, 2, 3]
b = a[:2] # 👀 a completely new list!
b[0] = 999
>>> a
[1, 2, 3]
>>> b
[999, 2]
However if you try the same thing in Go, you get something else.
func main() {
data := []int{1, 2, 3}
slice := data[:2]
slice[0] = 999
fmt.Println(data)
fmt.Println(slice)
}
[999 2 3]
[999 2]
Lesson
In Go, a slice shares the same backing array and capacity as the original. So if you change an element in the slice, the original contents are modified as well.
The Fix
If you want to get an independent slice, you have two options.
// Option #1
// appending elements to a nil slice
// `...` changes slice to arguments for the variadic function `append`
a := append([]int{}, data[:2]...)
// Option #1
// Create slice with length of 2
// copy(dest, src)
a := make([]int, 2)
copy(a, data[:2])
And according to StackOverflow, the append
option is slightly faster than the make. + copy
option!
« Things we can learn from Microsoft
Go test your tests in Go with go test »