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!
We'll cover the following
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 1400+ tech skills courses.