Slide 1

Slide 1 text

Golang sucks Marek Majkowski @majek04

Slide 2

Slide 2 text

Context: Cloudflare 2

Slide 3

Slide 3 text

Don't get me wrong 3

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

Golang is as easy as Python 5

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Can Golang divide? • Python: 1 % -2 = ? • Golang: 1 % -2 = ? 7 -1 1

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

11 func main() { fmt.Println(-11 / 2, -11 >> 1) } -5 -6 shift is the same as division?

Slide 12

Slide 12 text

12 func main() { var a uint8 a = 1 a = a << 32 fmt.Println(a) } 0 in C it's 1

Slide 13

Slide 13 text

Documentation 13

Slide 14

Slide 14 text

14

Slide 15

Slide 15 text

15

Slide 16

Slide 16 text

Documentation 16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

18

Slide 19

Slide 19 text

Golang is better than C 19

Slide 20

Slide 20 text

Garbage collector 20

Slide 21

Slide 21 text

21

Slide 22

Slide 22 text

22 GOGC=11300

Slide 23

Slide 23 text

23

Slide 24

Slide 24 text

container/list 24

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

26 (Item) (Element) ⟯ ⟯

Slide 27

Slide 27 text

GC + lack of manual memory management != speed 27

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

&^= operator 29 func main() { var a uint32 = 0xdeadbabe a &^= 0xffff0000 fmt.Printf("%08x\n", a) } 0000babe

Slide 30

Slide 30 text

Overflows 30 illegal: a := uint64(18446744073709551615)+1; legal: a := uint64(18446744073709551615); a = a + 1

Slide 31

Slide 31 text

x == Nan 31 package main import ( "fmt" "math" ) func main() { a := float32(math.NaN()) fmt.Printf("%f\n", a) }

Slide 32

Slide 32 text

Mandatory downcasting 32 package main import ( "fmt" ) func main() { var a int64 var b int32 b = 1 a = int64(b) fmt.Println(a) }

Slide 33

Slide 33 text

but casting to interfaces is fine 33

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

35 package main import ( "fmt" ) func main() { for { switch 1 { default: break // !!! } } fmt.Println("Done!") }

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Golang is good for network programming 37

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

39

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Is FD signed or unsigned? 41

Slide 42

Slide 42 text

Signed vs unsigned 42 • shift >> operator requires unsigned int • len() returns signed int

Slide 43

Slide 43 text

Fd from File is OK 43

Slide 44

Slide 44 text

Fd from Socket is NOT OK 44

Slide 45

Slide 45 text

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) }

Slide 46

Slide 46 text

TCP Backlog 46

Slide 47

Slide 47 text

TCP Backlog 47

Slide 48

Slide 48 text

For network programming access to BSD API is necessary. 48

Slide 49

Slide 49 text

time.Now went backwards 49

Slide 50

Slide 50 text

Lack of monotonic time 50

Slide 51

Slide 51 text

51

Slide 52

Slide 52 text

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 }

Slide 53

Slide 53 text

...runes are just int32.... 53

Slide 54

Slide 54 text

Good for concurrency 54

Slide 55

Slide 55 text

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) }

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Good libraries 58

Slide 59

Slide 59 text

59 go sort

Slide 60

Slide 60 text

go sort is two way 60

Slide 61

Slide 61 text

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; }

Slide 62

Slide 62 text

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 ???? }

Slide 63

Slide 63 text

Modern language 63

Slide 64

Slide 64 text

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 {

Slide 65

Slide 65 text

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? }

Slide 66

Slide 66 text

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? } }

Slide 67

Slide 67 text

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)) }

Slide 68

Slide 68 text

compare interface 68 (interface{})(0) != (interface{})(uint(0))

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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)

Slide 72

Slide 72 text

Assertion vs Conversion • v = aType(t) // type conversion • v = t.(aType) // type assertion 72

Slide 73

Slide 73 text

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") }

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Golang does not help with smooth API changes 75

Slide 76

Slide 76 text

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) }

Slide 77

Slide 77 text

interface{}(nil) vs nil 77

Slide 78

Slide 78 text

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() }

Slide 79

Slide 79 text

Errors 79

Slide 80

Slide 80 text

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 }

Slide 81

Slide 81 text

Golang doesn't help with extracting meaning from errors. 81

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Global namespace NEEDS relative imports 85

Slide 86

Slide 86 text

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)

Slide 87

Slide 87 text

87

Slide 88

Slide 88 text

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