👈

Learning Go

Author: Jon Bodner

Last Accessed on Kindle: Jun 10 2022

Ref: Amazon Link

Most languages allow a great deal of flexibility on how code is laid out. Go does not. Enforcing a standard format makes it a great deal easier to write tools that manipulate source code. This simplifies the compiler and allows the creation of some clever tools for generating code.

Since Go defines a standard way of formatting code, Go developers avoid arguments over One True Brace Style and Tabs vs. Spaces, For example, Go programs use tabs to indent, and it is a syntax error if the opening brace is not on the same line as the declaration or command that begins the block.

There’s an enhanced version of go fmt available called goimports that also cleans up your import statements. It puts them in alphabetical order, removes unused imports, and attempts to guess any unspecified imports. Its guesses are sometimes inaccurate, so you should insert imports yourself.

I recommend that you start off using go vet as a required part of your automated build process and golint as part of your code review process (since golint might have false positives and false negatives, you can’t require your team to fix every issue it reports). Once you are used to their recommendations, try out golangci-lint and tweak its settings until it works for your team.

Go doesn’t allow automatic type promotion between variables. You must use a type conversion when variable types do not match. Even different-sized integers and floats must be converted to the same type to interact. This makes it clear exactly what type you want without having to memorize any type conversion rules

Go doesn’t allow truthiness. In fact, no other type can be converted to a bool, implicitly or explicitly. If you want to convert from another data type to boolean, you must use one of the comparison operators

There is one limitation on :=. If you are declaring a variable at package level, you must use var because := is not legal outside of functions.

Avoid declaring variables outside of functions because they complicate data flow analysis.

Note The Go compiler won’t stop you from creating unread package-level variables. This is one more reason why you should avoid creating package-level variables.

Go considers the size of the array to be part of the type of the array. This makes an array that’s declared to be [3]int a different type from an array that’s declared to be [4]int. This also means that you cannot use a variable to specify the size of an array, because types must be resolved at compile time, not at runtime.

The main reason why arrays exist in Go is to provide the backing store for slices, which are one of the most useful features of Go.

Nil is an identifier that represents the lack of a value for some types. Like the untyped numeric constants we saw in the previous chapter, nil has no type, so it can be assigned or compared against values of different types. A nil slice contains nothing.

The reflect package contains a function called DeepEqual that can compare almost anything, including slices. It’s primarily intended for testing, but you could use it to compare slices if you needed to.

Functions like len are built in to Go because they can do things that can’t be done by the functions that you can write. We’ve already seen that len’s parameter can be any type of array or any type of slice. We’ll soon see that it also works for strings and maps.

Every time you pass a parameter to a function, Go makes a copy of the value that’s passed in. Passing a slice to the append function actually passes a copy of the slice to the function. The function adds the values to the copy of the slice and returns the copy. You then assign the returned slice back to the variable in the calling function.

Every high-level language relies on a set of libraries to enable programs written in that language to run, and Go is no exception. The Go runtime provides services like memory allocation and garbage collection, concurrency support, networking, and implementations of built-in types and functions. The Go runtime is compiled into every Go binary. This is different from languages that use a virtual machine, which must be installed separately to allow programs written in those languages to function. Including the runtime in the binary makes it easier to distribute Go programs and avoids worries about compatibility issues between the runtime and the program.

Even though Go allows you to use slicing and indexing syntax with strings, you should only use it when you know that your string only contains characters that take up one byte.

There are two common situations where anonymous structs are handy. The first is when you translate external data into a struct or a struct into external data (like JSON or protocol buffers). This is called unmarshaling and marshaling data. We’ll learn how to do this in “encoding/json”. Writing tests is another place where anonymous structs pop up. We’ll use a slice of anonymous structs when writing table-driven tests in Chapter 13.

Note that the condition has a leading ! to negate the condition from the Java code. The Go code is specifying how to exit the loop, while the Java code specifies how to stay in it. Go also includes the continue keyword, which skips over the rest of the body of a for loop and proceeds directly to the next iteration. Technically, you don’t need a continue statement. You could write code like Example 4-11. Example 4-11. Confusing code for i := 1; i <= 100; i++ {     if i%3 == 0 {         if i%5 == 0 {             fmt.Println(“FizzBuzz”)         } else {             fmt.Println(“Fizz”)         }     } else if i%5 == 0 {         fmt.Println(“Buzz”)     } else {         fmt.Println(i)     } } But this is not idiomatic. Go encourages short if statement bodies, as left-aligned as possible. Nested code is difficult to follow. Using a continue statement makes it easier to understand what’s going on.

If maps always hash items to the exact same values, and you know that a server is storing some user data in a map, you can actually slow down a server with an attack called Hash DoS by sending it specially crafted data where all of the keys hash to the same bucket.

Favor blank switch statements over if/else chains when you have multiple related cases. Using a switch makes the comparisons more visible and reinforces that they are a related set of concerns.