Golang sucks

D4e1d473a995ef37b3e03e9e6006c3e3?s=47 majek04
November 30, 2017

Golang sucks

I found my notes from learning golang couple of years ago. Here are some things that caught me by surprise.

D4e1d473a995ef37b3e03e9e6006c3e3?s=128

majek04

November 30, 2017
Tweet

Transcript

  1. Golang sucks Marek Majkowski @majek04

  2. Context: Cloudflare 2

  3. Don't get me wrong 3

  4. 4

  5. Golang is as easy as Python 5

  6. Can Golang divide? • Python: -1 / 2 = ?

    • Golang: -1 / 2 = ? 6 -1 -0.5 0
  7. Can Golang divide? • Python: 1 % -2 = ?

    • Golang: 1 % -2 = ? 7 -1 1
  8. 8 func main() { var a int8 a = -127

    / -1; fmt.Println(a) } 127
  9. 9 func main() { var a int8 a = -128

    / -1; fmt.Println(a) } error: constant 128 overflows int8
  10. 10 func main() { var a int8 a = -127

    - 1; a = a / -1; fmt.Println(a) } -128
  11. 11 func main() { fmt.Println(-11 / 2, -11 >> 1)

    } -5 -6 shift is the same as division?
  12. 12 func main() { var a uint8 a = 1

    a = a << 32 fmt.Println(a) } 0 in C it's 1
  13. Documentation 13

  14. 14

  15. 15

  16. Documentation 16

  17. 17

  18. 18

  19. Golang is better than C 19

  20. Garbage collector 20

  21. 21

  22. 22 GOGC=11300

  23. 23

  24. container/list 24

  25. 25 (Element) (Item) (struct in C)

  26. 26 (Item) (Element) ⟯ ⟯

  27. GC + lack of manual memory management != speed 27

  28. Unary negation 28 packet.go:104: the bitwise complement operator is ^

  29. &^= operator 29 func main() { var a uint32 =

    0xdeadbabe a &^= 0xffff0000 fmt.Printf("%08x\n", a) } 0000babe
  30. Overflows 30 illegal: a := uint64(18446744073709551615)+1; legal: a := uint64(18446744073709551615);

    a = a + 1
  31. x == Nan 31 package main import ( "fmt" "math"

    ) func main() { a := float32(math.NaN()) fmt.Printf("%f\n", a) }
  32. Mandatory downcasting 32 package main import ( "fmt" ) func

    main() { var a int64 var b int32 b = 1 a = int64(b) fmt.Println(a) }
  33. but casting to interfaces is fine 33

  34. 34 package main import ( "fmt" ) func main() {

    for { switch 1 { case 1: default: } } } Break
  35. 35 package main import ( "fmt" ) func main() {

    for { switch 1 { default: break // !!! } } fmt.Println("Done!") }
  36. 4k on stack? 36 package main func main() { fmt.Println("Start",

    objects()) a := make([]byte, 1 << 18) a[1] = 1 fmt.Println("slice of 1<<18", objects()) b := make([]byte, 1 << 19) b[1] = 1 fmt.Println("sloce of 1<<19", objects()) fmt.Println(">", a[:2], b[:2]) } https://play.golang.org/p/mG01rYDHk8
  37. Golang is good for network programming 37

  38. TCPConn.Dial() 38 • bind-before-connect • SO_REUSEPORT

  39. 39

  40. 40 ... impossible to emulate non-blocking connect()...

  41. Is FD signed or unsigned? 41

  42. Signed vs unsigned 42 • shift >> operator requires unsigned

    int • len() returns signed int
  43. Fd from File is OK 43

  44. Fd from Socket is NOT OK 44

  45. 45 func getTCPSocketFd(socket interface{}) int { // fd is platform-specific

    and not exported socketValue := reflect.ValueOf(socket).Elem() fdValue := socketValue.FieldByName("fd") if !fdValue.IsValid() { panic("socket missing fd on this platform") } netFDValue := fdValue.Elem() sysFDValue := netFDValue.FieldByName("sysfd") if !sysFDValue.IsValid() { panic("socket missing fd on this platform") } return int(sysFD) }
  46. TCP Backlog 46

  47. TCP Backlog 47

  48. For network programming access to BSD API is necessary. 48

  49. time.Now went backwards 49

  50. Lack of monotonic time 50

  51. 51

  52. byte slice and string and utf8 • String holds arbitrary

    bytes. It is not required to hold Unicode text. • Range on string is magic: • Fact #3: identifiers are only exported if they’re part of the Unicode uppercase class 52 const nihongo = "⽇日本語" for index, runeValue := range nihongo { fmt.Printf("%#U starts at byte position %d\n", runeValue, i }
  53. ...runes are just int32.... 53

  54. Good for concurrency 54

  55. By value or by reference? 55 func Foo(a T) {

    c := "b" *a = *T(&c) } type T *string func main() { c := "a" a := T(&c) Foo(a) fmt.Printf("%q\n", *a) }
  56. By value or by reference? 56 • String --> immutable

    • Pointer to String --> value is immutable • Struct --> mutable, pass-by-value is copying • Pointer to Struct --> mutable • Slice of structs --> mutable • Slice of pointers to structs --> mutable • Maps --> mutable • Chans --> mutable
  57. Concurrency is half-baked • map is not thread safe (need

    to use lock) • especially problematic for slow-iterations • Channels are slow • Mutex is still useful • Channels and network don't integrate • But NET package is blocking and has no multiplexing 57
  58. Good libraries 58

  59. 59 go sort

  60. go sort is two way 60

  61. C qsort is three-way 61 int compare_ints(const void *p, const

    void *q) { int x = *(const int *)p; int y = *(const int *)q; if (x < y) return -1; else if (x > y) return 1; return 0; }
  62. Difference? 62 type T struct { foo Foo bar Bar

    } func Less(a *T, b *T) { if FooLess(a.foo, b.foo) { return BarLess(a.bar, b.bar) } else ???? }
  63. Modern language 63

  64. 64 var fd *os.File var err error if fd, err

    = listenConn.File(); err == nil { var fd *os.File var err error if fd, err := listenConn.File(); err == nil {
  65. What is an enum? 65 type Stereotype int const (

    TypicalNoob Stereotype = iota // 0 TypicalHipster // 1 ) func main() { var s Stereotype var i int s = 6 // What type is s? i = int(s) // What type is s? i = int(TypicalNoob) // What type is TypicalNoob? s = TypicalNoob + 1 // What type is TypicalNoob? }
  66. 66 type Stereotype int const ( TypicalNoob Stereotype = iota

    // 0 TypicalHipster // 1 ) func main() { var s Stereotype s = 6 switch s { case TypicalNoob: fmt.Println("Noob") case TypicalHipster: fmt.Println("Hipster") default: // How come "s" ever get here? } }
  67. compare interface 67 func is_blank(value interface{}) bool { switch value.(type)

    { case int: if value == 0 { return true } case uint: if value == 0 { return true } } return false } func main() { var my_val uint = 0 fmt.Println(is_blank(my_val)) }
  68. compare interface 68 (interface{})(0) != (interface{})(uint(0))

  69. 69 func is_blank(value interface{}) bool { switch value.(type) { case

    int: if value == 0 { return true } case uint: if value == uint(0) { // !!!!! return true } } return false } compare interface
  70. 70 func is_blank(value interface{}) bool { switch value := value.(type)

    { // !!! case int: if value == 0 { return true } case uint: if value == 0 { return true } } return false } compare interface
  71. Switching on type 71 package main import ( "fmt" )

    func main() { var i int switch i.(type) { case int: fmt.Println("0") case string: fmt.Println("1") } } > cannot type switch on non-interface value i (type int)
  72. Assertion vs Conversion • v = aType(t) // type conversion

    • v = t.(aType) // type assertion 72
  73. Casting and interface 73 type Lockable interface { Lock() }

    func main() { var a = Foo() l := a.(Lockable) fmt.Println("lockable") } type Lockable interface { Lock() } func main() { var a = Foo() l := Lockable(a) fmt.Println("lockable") }
  74. API evolution • While refactoring my code, can I turn

    a struct into an interface with minimal changes? YES • Can I turn a struct into an interface with zero changes for users of my library? NO • To convert whatever-you-got from API to some interface (Lockable) you need to know if it's a struct, pointer to struct or interface. • It makes sense to cast everything to interface{}. 74
  75. Golang does not help with smooth API changes 75

  76. Structure inlining 76 type A struct {} type B struct

    {} func (*A)Lock(){ fmt.Println("A") } func (*B)Lock(){ fmt.Println("B") } type C struct { A B } func main() { var c C // c.Lock() // ambiguous selector c.A.Lock() fmt.Println(c) }
  77. interface{}(nil) vs nil 77

  78. 78 type I interface { F() } type S struct{}

    func (p *S) F() { fmt.Printf("p = %v\n", p) } func main() { // A nil interface value var i I fmt.Printf("i == nil: %t\n\n", i == nil) // A non-nil inteface value, containing a nil value of // underlying type: i = (*S)(nil) fmt.Printf("i == nil: %t\n", i == nil) fmt.Printf("i.(*S) == nil: %t\n", i.(*S) == nil) // We can even call methods of S on it: i.F() }
  79. Errors 79

  80. Errno 80 func ExtractErrno(err error) int { if pe, ok

    := err.(*ProxyError); ok == true { err = pe.Err } if pe, ok := err.(*os.PathError); ok == true { err = pe.Err } if ope, ok := err.(*net.OpError); ok == true { err = ope.Err } if sce, ok := err.(*os.SyscallError); ok == true { err = sce.Err } if se, ok := err.(syscall.Errno); ok == true { return int(se) } return 0 }
  81. Golang doesn't help with extracting meaning from errors. 81

  82. Three indices 82 var array [10]int slice := array[2:4] slice

    = array[2:4:7]
  83. Vendoring is a mess • Fact #18: if you change

    your github username, you break every package that depended on one of your packages (extra transition milestone!) • golang prefers the use of global namespace for modules which is wrong 83
  84. Copy is fine, with relative imports • github.com/cloudflare/golibs • github.com/cloudflare/golibs/foo

    • github.com/cloudflare/golibs/bar • Copy / rename is impossible 84
  85. Global namespace NEEDS relative imports 85

  86. new vs make 86 // Allocate enough memory to store

    a bytes.Buffer value // and return a pointer to the value's address. var buf bytes.Buffer p := &buf // Use a composite literal to perform allocation and // return a pointer to the value's address. p := &bytes.Buffer{} // Use the new function to perform allocation, which will // return a pointer to the value's address. p := new(bytes.Buffer)
  87. 87

  88. Thanks! • Solid • Fast • Maintainable 88 marek@cloudflare.com @majek04