Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Understanding Nil

Understanding Nil

Opening Keynote at GopherCon 2016

Is it a constant? A variable? Where is it defined? What is its type? It has no type? It has all the types? Those are usual questions that people learning Go ask and this talk answers all of them and more.

The talk covers the different contexts where nil is used, what it means, the gotchas to be aware of, and some patterns where nil becomes essential.

In parallel to the exploration of how nil can be used I will also discuss the origin and history of nil in those contexts.

Francesc Campoy Flores

July 11, 2016
Tweet

More Decks by Francesc Campoy Flores

Other Decks in Programming

Transcript

  1. agenda what is nil? what is nil in Go? what

    does nil mean? is nil useful?
  2. etymology nil Latin nihil meaning nothing null Latin ne +

    ullus meaning not any none Old English ne + ān meaning not one
  3. names for the number zero in English - zero -

    cipher - null - love - duck - nil - nada - zilch - zip - the letter ‘o’ - naught - nought - aught - ought source: wikipedia
  4. “I call it my billion-dollar mistake. It was the invention

    of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language.” - Sir C.A.R. Hoare
  5. bool → false numbers→ 0 string → "" zero values

    pointers → nil slices → nil maps → nil channels → nil functions → nil interfaces → nil
  6. zero values for struct types type Person struct { AgeYears

    int Name string Friend []Person } var p Person // Person{0, "", nil}
  7. “... unless the value is the predeclared identifier nil, which

    has no type” - Go language specification
  8. untyped zero a := false a := "" a :=

    0 // a := int(0) a := 0.0 // a := float64(0) a := nil // use of untyped nil
  9. “nil is a predeclared identifier representing the zero value for

    a pointer, channel, func, interface, map, or slice type.” - Go documentation for “builtin” package
  10. “nil is a predeclared identifier representing the zero value for

    a pointer, channel, func, interface, map, or slice type.” - Go documentation for “builtin” package
  11. twenty-five* keywords break default func interface select case defer go

    map struct chan else goto package switch const fallthrough if range type continue for import return var * plus the five secret ones, obviously
  12. // YOLO var nil = errors.New(“¯\_(ツ)_/¯”) * For extra evilness:

    place at the end of doc.go predefined vs keyword
  13. - they point to a position in memory - similar

    to C or C++, but - no pointer arithmetic → memory safety - garbage collection pointers in Go
  14. a slice with five elements s := make([]byte, 5) 5

    5 0 0 0 0 0 []byte [5]byte ptr len cap
  15. var p *Person // nil of type *Person var s

    fmt.Stringer = p // Stringer (*Person, nil) fmt.Println(s == nil) // false
  16. func do() error { var err *doError return err }

    func main() { err := do() fmt.Println(err == nil) } when is nil not nil? // nil of type *doError // error (*doError, nil) // error (*doError, nil) // false
  17. func do() *doError { return nil } func main() {

    err := do() fmt.Println(err == nil) } // nil of type *doError // nil of type *doError // true nil is not nil
  18. func do() *doError { return nil } func wrapDo() error

    { return do() } func main() { err := wrapDo() fmt.Println(err == nil) } nil is not nil // nil of type *doError // error (*doError, nil) // nil of type *doError // error (*doError, nil) // false
  19. pointers point to nothing slices have no backing array maps

    are not initialized channels are not initialized functions are not initialized interfaces have no value assigned, not even a nil pointer kinds of nil
  20. var p *int p == nil // true *p //

    panic: invalid memory address or nil pointer dereference pointers
  21. type tree struct { v int l *tree r *tree

    } func (t *tree) Sum() int implement Sum 6 2 1 7 3 4 5 8 9
  22. a first solution func (t *tree) Sum() int { sum

    := t.v if t.l != nil { sum += t.l.Sum() } if t.r != nil { sum += t.r.Sum() } return sum } 6 2 1 7 3 4 5 8 9
  23. Code repetition: if v != nil { v.m() } Panic

    when t is nil var t *tree sum := t.Sum() // panic: invalid memory address or nil pointer dereference issues
  24. type person struct {} func sayHi(p *person) { fmt.Println(“hi”) }

    func (p *person) sayHi() { fmt.Println(“hi”) } var p *person p.sayHi() // hi pointer receivers
  25. func (t *tree) Sum() int { if t == nil

    { return 0 } return t.v + t.l.Sum() + t.r.Sum() } nil receivers are useful: Sum
  26. func (t *tree) String() string { if t == nil

    { return "" } return fmt.Sprint(t.l, t.v, t.r) } nil receivers are useful: String
  27. func (t *tree) Find(v int) bool { if t ==

    nil { return false } return t.v == v || t.l.Find(v) || t.r.Find(v) } nil receivers are useful: Find
  28. var s []slice len(s) // 0 cap(s) // 0 for

    range s // iterates zero times s[i] // panic: index out of range nil slices
  29. var s []int for i := 0; i < 10;

    i++ { fmt.Printf("len: %2d cap: %2d\n", len(s), cap(s)) s = append(s, i) } append on nil slices
  30. len: 0 cap: 0 [] len: 1 cap: 1 [0]

    len: 2 cap: 2 [0 1] len: 3 cap: 4 [0 1 2] len: 4 cap: 4 [0 1 2 3] len: 5 cap: 8 [0 1 2 3 4] len: 6 cap: 8 [0 1 2 3 4 5] len: 7 cap: 8 [0 1 2 3 4 5 6] len: 8 cap: 8 [0 1 2 3 4 5 6 7] len: 9 cap: 16 [0 1 2 3 4 5 6 7 8] → s is nil! → allocation → reallocation → reallocation → reallocation → reallocation $ go run slices.go
  31. nil maps var m map[t]u len(m) // 0 for range

    m // iterates zero times v, ok := m[i] // zero(u), false m[i] = x // panic: assignment to entry in nil map
  32. using maps func NewGet(url string, headers map[string]string) (*http.Request, error) {

    req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } for k, v := range headers { req.Header.Set(k, v) } return req, nil }
  33. var c chan t <- c // blocks forever c

    <- x // blocks forever close(c) // panic: close of nil channel nil channels
  34. func merge(out chan<- int, a, b <-chan int) { for

    { select { case v := <-a: out <- v case v := <-b: out <- v } } } a naive solution
  35. 2 2 1 2 2 1 2 1 2 1

    2 1 2 1 1 2 2 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 … $ go run naive.go
  36. var c chan t v, ok <- c // zero(t),

    false c <- x // panic: send on closed channel close(c) // panic: close of nil channel closed channels
  37. func merge(out chan<- int, a, b <-chan int) { for

    { select { case v := <-a: out <- v case v := <-b: out <- v } } } a naive solution
  38. checking for closed chans case v, ok := <-a: if

    !ok { aClosed = true continue } out <- v // analogous code for case b
  39. checking for closed chans func merge(out chan<- int, a, b

    <-chan int) { var aClosed, bClosed bool for !aClosed || !bClosed { select { case v, ok := <-a: if !ok { aClosed = true; continue } out <- v case v, ok := <-b: if !ok { bClosed = true; continue } out <- v } } }
  40. $ go run checkForClosed.go 1 1 2 1 1 2

    2 1 1 2 1 2 2 1 2 1 2 1 2 2 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /Users/campoy/src/github.com/campoy/talks/nil/talk/code/chans. go:97 +0x15a exit status 2
  41. and closing channel out func merge(out chan<- int, a, b

    <-chan int) { var aClosed, bClosed bool for !aClosed || !bClosed { select { case v, ok := <-a: if !ok { aClosed = true; continue } out <- v case v, ok := <-b: if !ok { bClosed = true; continue } out <- v } } close(out) }
  42. and closing channel out func merge(out chan<- int, a, b

    <-chan int) { var aClosed, bClosed bool for !aClosed || !bClosed { select { case v, ok := <-a: if !ok { aClosed = true; continue } out <- v case v, ok := <-b: if !ok { bClosed = true; continue } out <- v } } close(out) }
  43. 1 1 2 1 1 2 2 1 1 2

    1 2 2 1 2 1 2 1 2 2 $ go run closingOut.go
  44. let’s log case v, ok := <-a: if !ok {

    aClosed = true fmt.Println("a is now closed") continue } out <- v // analogous code for case b
  45. $ go run withLogs.go 1 1 2 1 1 2

    2 1 1 2 1 2 2 1 2 1 2 1 2 2 b is now closed … b is now closed 1 b is now closed … b is now closed 1 a is now closed
  46. var aClosed, bClosed bool for !aClosed || !bClosed { select

    { case v, ok := <-a: if !ok { aClosed = true; continue } out <- v case v, ok := <-b: if !ok { bClosed = true; continue } out <- v } } close(out) let’s log
  47. var c chan t <- c // blocks forever c

    <- x // blocks forever close(c) // panic: close of nil channel nil channels
  48. switching off a channel case v, ok := <-a: if

    !ok { aClosed = true fmt.Println("a is now closed") continue } out <- v // analogous code for case b
  49. case v, ok := <-a: if !ok { a =

    nil fmt.Println("a is now closed") continue } out <- v // analogous code for case b switching off a channel
  50. func merge(out chan<- int, a, b <-chan int) { for

    a != nil || b != nil { select { case v, ok := <-a: if !ok { a = nil; continue } out <- v case v, ok := <-b: if !ok { b = nil; continue } out <- v } } close(out) } switching off a channel; no logs
  51. 1 1 2 1 1 2 2 1 1 2

    1 2 2 1 2 1 2 1 2 2 $ go run closingOut.go
  52. Go has first-class functions functions can be used as struct

    fields they need a zero value; logically it is nil nil funcs type Foo struct { f func() error }
  53. lazy initialization of variables nil can also imply default behavior

    func NewServer(logger func(string, …interface{})) { if logger == nil { logger = log.Printf } logger("initializing %s", os.Getenv("hostname")) … } nil funcs for default values
  54. type ints []int func (i ints) Sum() int { s

    := 0 for _, v := range i { s += v } return s } summer
  55. func doSum(s Summer) int { if s == nil {

    return 0 } return s.Sum() } nil values and default values
  56. nil values and default values var t *tree doSum(t) //

    (*tree, nil) var i ints doSum(i) // (ints, nil) doSum(nil) // (nil, nil)
  57. pointers methods can be called on nil receivers slices perfectly

    valid zero values maps perfect as read-only values channels essential for some concurrency patterns functions needed for completeness interfaces the most used signal in Go (err != nil) nil is useful