Slide 1

Slide 1 text

Go Gotchas Mike Kaperys

Slide 2

Slide 2 text

Hey! I’m Mike, I’m a software engineer on the telecoms team at Utility Warehouse (we’re hiring!). I’ve been using Go for around 2 and a half years, and a software engineer for around 5. I’m a co-organiser of GoSheffield. Please come and speak to us if you’re interested in giving a talk! @_kaperys kaperys.io

Slide 3

Slide 3 text

We’ll cover... ● Some counter-intuitive language gotchas ● Some common mistakes and misconceptions ● Some things you might not know

Slide 4

Slide 4 text

package main import "fmt" type customError struct{} func (customError) Error() string { return "" } func main() { fmt.Println(f() == nil) } func f() error { var err *customError fmt.Println(err == nil) return err }

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Why? An interface contains two values under the hood - a type (T), and a value (V). For an interface to equate to nil, both values must be unset. ”An interface value is nil only if the V and T are both unset, (T=nil, V is not set) [...]. Such an interface value will therefore be non-nil even when the pointer value V inside is nil.” https://golang.org/doc/faq#nil_error

Slide 7

Slide 7 text

package main import "fmt" type customError struct{} func (customError) Error() string { return "" } func main() { fmt.Println(f() == nil) // } func f() error { var err *customError // nil fmt.Println(err == nil) return err // conversion to error interface }

Slide 8

Slide 8 text

package main import "fmt" func main() { var src, dst []int src = []int{1, 2, 3} copy(dst, src) fmt.Println("dst:", dst) }

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Why? The number of elements copied by the copy function is min(a, b). Therefore, the destination slice, a, must be allocated with enough space for len(b) elements. ”Both arguments must have identical element type T and must be assignable to a slice of type []T. The number of elements copied is the minimum of len(src) and len(dst). As a special case, copy also accepts a destination argument assignable to type []byte with a source argument of a string type.” https://golang.org/ref/spec#Appending_and_copying_slices

Slide 11

Slide 11 text

package main import "fmt" func main() { var src, dst []int src = []int{1, 2, 3} dst = make([]int, len(src)) copy(dst, src) fmt.Println("dst:", dst) // dst: [1 2 3] }

Slide 12

Slide 12 text

package main import ( "fmt" "strings" ) func main() { res := strings.TrimRight("ABBA", "BA") fmt.Println(res) }

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Why? The Trim, TrimLeft and TrimRight functions remove all unicode code points in the cutset (the second argument). Therefore, all As and Bs are removed from the string. ”TrimRight returns a slice of the string s, with all trailing Unicode code points contained in cutset removed. To remove a suffix, use TrimSuffix instead.” https://golang.org/pkg/strings/#TrimRight

Slide 15

Slide 15 text

package main import ( "fmt" "strings" ) func main() { res := strings.TrimSuffix("ABBA", "BA") fmt.Println(res) // AB }

Slide 16

Slide 16 text

package main import "log" func main() { for { switch { case true: break default: log.Fatal("I don't know what to do!") } log.Println("We broke out of the switch!") } log.Println("We broke out of the loop!") }

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Why? The break keyword breaks only the innermost for, switch or select statement. Therefore breaking out of the switch, but remaining inside the for loop. Using a “label” enables break (as well as continue and goto) to target a specific statement. https://golang.org/ref/spec#Break_statements

Slide 19

Slide 19 text

package main import "log" func main() { loop: for { // `break loop` breaks this for switch { // Previously `break` broke this switch case true: break loop default: log.Fatal("I don't know what to do!") } log.Println("We broke out of the switch!") } log.Println("We broke out of the loop!") }

Slide 20

Slide 20 text

package main import "fmt" func main() { fmt.Println(100 + 010 + 001) }

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Why? An integer type prefixed with 0 indicates a base 8 value. So in this case, we’re computing 100 + 8 (010) + 1 (001). “An optional prefix sets a non-decimal base: 0 for octal, 0x or 0X for hexadecimal.” https://golang.org/ref/spec#Integer_literals

Slide 23

Slide 23 text

package main import "fmt" func main() { fmt.Println(100 + 10 + 1) // 111 }

Slide 24

Slide 24 text

package main import ( "fmt" "time" ) func main() { values := []string{"A", "B", "C"} for _, val := range values { go func() { fmt.Println(val) }() } time.Sleep(1 * time.Second) }

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Why? A variable captured in a closure is evaluated at the time the closure is executed. Therefore, our example prints “C, C, C” because each goroutine uses the same val variable. “When the closure runs, it prints the value of v at the time fmt.Println is executed, but v may have been modified since the goroutine was launched.” https://golang.org/doc/faq#closures_and_goroutines

Slide 27

Slide 27 text

package main import ( "fmt" "time" ) func main() { values := []string{"A", "B", "C"} for _, val := range values { go func(val string) { fmt.Println(val) // val is now local }(val) // inject the variable } time.Sleep(1 * time.Second) }

Slide 28

Slide 28 text

package main import ( "fmt" "unsafe" ) type myStruct struct { myInt bool // 1 byte myFloat float64 // 8 bytes myBool int32 // 4 bytes } func main() { a := myStruct{} fmt.Println(unsafe.Sizeof(a)) }

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Why? Memory is allocated consecutively in chunks of 8 bytes. https://medium.com/@felipedutratine/how-to-organize-the-go-struct-in-order-to-save-memory-c78afcf59ec2 8 + 8 + 8 = 24 bytes. We can optimise the struct by rearranging its properties.

Slide 31

Slide 31 text

Why? Moving the float64 to the top of the struct changes the way the memory is allocated. https://medium.com/@felipedutratine/how-to-organize-the-go-struct-in-order-to-save-memory-c78afcf59ec2 Now, the float64 occupies the first 8 bytes. The following bool and int32 are padded in the following 8 bytes to respect data alignment. 8 + 8 = 16 bytes.

Slide 32

Slide 32 text

package main import ( "fmt" "unsafe" ) type myStruct struct { myFloat float64 // 8 bytes myInt bool // 1 byte myBool int32 // 4 bytes } func main() { a := myStruct{} fmt.Println(unsafe.Sizeof(a)) // 16 }

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Tooling to the rescue! ● cmd/vet (golang.org/cmd/vet) ● staticcheck (staticcheck.io) ● golangci-lint (github.com/golangci/golangci-lint) ● go-critic (go-critic.github.io) ● pprof (https://golang.org/pkg/runtime/pprof/)

Slide 35

Slide 35 text

Thanks! Questions? @_kaperys kaperys.io