Go Gotchas

I have had the good fortune of being able to use Go in my current job. I like the language a lot, it is pretty straightforward. I like that it is typed for safety. I like that the language features are mostly orthogonal. There are however, a few things I find inconsistent, confusing, or surprising. I would like to document these here.

Calling a method on a nil pointer

package main

import "fmt"

type Godzilla struct {
  fireballs int
}

func (g *Godzilla) BlowFireball() {
  g.fireballs--
}

func (g *Godzilla) DoNothing() {
  fmt.Println("I'm doing nothing")
}

func main() {
  var godzilla *Godzilla
  godzilla.DoNothing()
  godzilla.BlowFireball()
}

What do you think the result of running this code is? I think it’s intuitive to think “there will be a runtime error when godzilla.DoNothing() is called, because godzilla is nil”. It’s true, godzilla is nil, but the surprising thing to me is that Go doesn’t care about that. We don’t encounter an error until g.fireballs-- is called. It seems that methods are just some syntactic sugar on top of plain old functions. Think about it this way: if you pass in a nil pointer to a function, it doesn’t fail at the time you call the function, it fails only once it tries to do something with that nil value. In the example above you can see

Copying structs that contain pointers

package main

import "fmt"

type Lawnmower struct {
  handsChopped int
  lastHandChopped *string
}

func main() {

  bill := "bill"
  husqvarna := Lawnmower{
    handsChopped:    5,
    lastHandChopped: &bill,
  }

  snapper := husqvarna
  snapper.handsChopped = 10

  fmt.Println("Husqvarna hands chopped:", husqvarna.handsChopped)
  fmt.Println("Snapper hands chopped:", snapper.handsChopped)

  bill = "hank"

  fmt.Println("Husqvarna last hand chopped:", *husqvarna.lastHandChopped)
  fmt.Println("Snapper last hand chopped:", *snapper.lastHandChopped)

  buck := "buck"
  snapper.lastHandChopped = &buck

  fmt.Println("Husqvarna last hand chopped:", *husqvarna.lastHandChopped)
  fmt.Println("Snapper last hand chopped:", *snapper.lastHandChopped)
}

It is a simple mistake to think “I copy this struct, and then everything is different so I can modify one without modifying the other”. Not so fast! Structs may contain pointer, and when they’re copied, they do not generate a new pointer and a new thing that pointer is pointing at, they just copy the value of the pointer. So unless you change the pointer itself, you may have an issue modifying one struct and inadvertently changing the other too.

In the example above, modifying the number of hands chopped by each lawnmower is no problem, they are just values, and they don’t interfere with each other. However, when we get to the line bill = "hank" both pointers are still pointing at the location of bill. Modifying the bill variable will change whose hand was last chopped in both lawnmowers.

If you have to modify what is in one struct but not the other, you need to declare a new variable and get a pointer to that.

This is a trivial example, but you can probably easily imagine how this can get really complicated when you want to copy a struct with other structs nested inside, any of which might contain pointers.

Using len() on strings that contain high unicode characters

package main

import (
	"fmt"
)

func main() {
	foo := "Föö" // ö is a 2-byte character in unicode
	
	fmt.Println(len(foo)) // prints 5, because this is counting bytes
	
	fmt.Println(len([]rune(foo))) // prints 3, what you probably want, because it is 3 discrete runes
	
	fmt.Println(foo[1:2]) // prints � because you just tried to cut a 2 byte character in half
}

Something that is definitely unintuitive about len() is that when you use it on a string it doens’t actually count the characters, it counts the bytes. It just so happens that many commonly used characters are one byte so this doesn’t matter, and it is likely to slip by the programmer unnoticed.

updated_at 05-06-2020