...

/

Highlights from the Previous Chapters

Highlights from the Previous Chapters

We often refer to the full explanation and examples in previous chapters. Here, each section summarizes all the concepts learned in this course and warns what not to do!

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:

Access this course and 40+ top-rated courses and projects.