Hiding a variable by misusing short declaration

In the following code snippet:

var remember bool = false
if something {
  remember := true // Wrong.
}
// use remember

The variable remember will never become true outside of the if-body. Inside the if-body, a new remember variable that hides the outer remember is declared because of :=, and there it will be true. However, after the closing } of if, the variable remember regains its outer value false. So, write it as:

if something {
  remember = true
}

This can also occur with a for-loop, and can be particularly subtle in functions with named return variables, as the following snippet shows:

func shadow() (err error) {
  x, err := check1() // x is created; err is assigned to
  if err != nil {
    return // err correctly returned
  }
  if y, err := check2(x); err != nil { // y and inner err are created
    return // inner err shadows outer err so err is wrongly returned!
  } else {
    fmt.Println(y)
  }
  return
}

Misusing strings

When you need to do a lot of manipulations on a string, think about the fact that strings in Go (like in Java and C#) are immutable. String concatenations of the kind a += b are inefficient, mainly when performed inside a loop. They cause many reallocations and the copying of memory. Instead, one should use a bytes.Buffer to accumulate string content, like in the following snippet:

var b bytes.Buffer
...
for condition {
  b.WriteString(str) // appends string str to the buffer
}
return b.String()

Remark: Due to compiler-optimizations and the size of the strings, using a buffer only starts to become more efficient when the number of concatenations is > 15.

Using defer for closing a file in the wrong scope

Suppose you are processing a range of files in a for-loop, and you want to make sure the files are closed after processing by using defer, like this:

for _, file := range files {
  if f, err = os.Open(file); err != nil {
    return
  }
  // This is wrong. The file is not closed when this loop iteration ends.
  defer f.Close()
  // perform operations on f:
  f.Process(data)
}

But, at the end of the for-loop, defer is not executed. Therefore, the files are not closed! Garbage collection will probably close them for you, but it can yield errors. Better do it like this:

for _, file := range files {
  if f, err = os.Open(file); err != nil {
    return
  }
  // perform operations on f:
  f.Process(data)
  // close f:
  f.Close()
}

The keyword defer is only executed at the return of a function, not at the end of a loop or some other limited scope.

Confusing new() and make()

We have talked about this and illustrated it with code already at great length in Chapter 5 and Chapter 8. The point is:

  • For slices, maps and channels: use make
  • For arrays, structs and all value types: use new

No need to pass a pointer to a slice to a function

A slice is a pointer to an underlying array. Passing a slice as a parameter to a function is probably what you always want, which means namely passing a pointer to a variable to be able to change it, and not passing a copy of the data. So, you want to do this:

func findBiggest( listOfNumbers []int ) int {}

not this:

func findBiggest( listOfNumbers *[]int ) int {}

Do not dereference a slice when used as a parameter!

Using pointers to interface types

Look at the following program which can’t be compiled:

Get hands-on with 1200+ tech skills courses.