$30 off During Our Annual Pro Sale. View Details »

Go Gotchas

Mike Kaperys
September 12, 2019

Go Gotchas

I’m sure everyone at some point has spent, probably, far longer than you should have, staring at a piece of code thinking “why the hell isn’t this working?!” - myself included. This talk somewhat aims to prevent that happening. We’ll cover some counter-intuitive language gotchas, some common mistakes and some general “tip hazards”.

Mike Kaperys

September 12, 2019
Tweet

More Decks by Mike Kaperys

Other Decks in Technology

Transcript

  1. Go Gotchas Mike Kaperys

  2. 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
  3. We’ll cover... • Some counter-intuitive language gotchas • Some common

    mistakes and misconceptions • Some things you might not know
  4. 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 }
  5. None
  6. 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
  7. package main import "fmt" type customError struct{} func (customError) Error()

    string { return "" } func main() { fmt.Println(f() == nil) // <T=*customError V=nil> } func f() error { var err *customError // nil fmt.Println(err == nil) return err // conversion to error interface }
  8. package main import "fmt" func main() { var src, dst

    []int src = []int{1, 2, 3} copy(dst, src) fmt.Println("dst:", dst) }
  9. None
  10. 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
  11. 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] }
  12. package main import ( "fmt" "strings" ) func main() {

    res := strings.TrimRight("ABBA", "BA") fmt.Println(res) }
  13. None
  14. 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
  15. package main import ( "fmt" "strings" ) func main() {

    res := strings.TrimSuffix("ABBA", "BA") fmt.Println(res) // AB }
  16. 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!") }
  17. None
  18. 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
  19. 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!") }
  20. package main import "fmt" func main() { fmt.Println(100 + 010

    + 001) }
  21. None
  22. 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
  23. package main import "fmt" func main() { fmt.Println(100 + 10

    + 1) // 111 }
  24. 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) }
  25. None
  26. 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
  27. 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) }
  28. 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)) }
  29. None
  30. 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.
  31. 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.
  32. 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 }
  33. None
  34. 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/)
  35. Thanks! Questions? @_kaperys kaperys.io