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.

D8e5d79ca42edc07693b9c1aacaa7e5e?s=128

Francesc Campoy Flores

July 11, 2016
Tweet

Transcript

  1. 2.
  2. 4.

    agenda what is nil? what is nil in Go? what

    does nil mean? is nil useful?
  3. 8.

    etymology nil Latin nihil meaning nothing null Latin ne +

    ullus meaning not any none Old English ne + ān meaning not one
  4. 9.

    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
  5. 12.
  6. 13.

    “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
  7. 16.
  8. 25.

    bool → false numbers→ 0 string → "" zero values

    pointers → nil slices → nil maps → nil channels → nil functions → nil interfaces → nil
  9. 26.

    zero values for struct types type Person struct { AgeYears

    int Name string Friend []Person } var p Person // Person{0, "", nil}
  10. 28.

    “... unless the value is the predeclared identifier nil, which

    has no type” - Go language specification
  11. 29.

    untyped zero a := false a := "" a :=

    0 // a := int(0) a := 0.0 // a := float64(0) a := nil // use of untyped nil
  12. 30.

    “nil is a predeclared identifier representing the zero value for

    a pointer, channel, func, interface, map, or slice type.” - Go documentation for “builtin” package
  13. 31.

    “nil is a predeclared identifier representing the zero value for

    a pointer, channel, func, interface, map, or slice type.” - Go documentation for “builtin” package
  14. 32.

    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
  15. 33.

    // YOLO var nil = errors.New(“¯\_(ツ)_/¯”) * For extra evilness:

    place at the end of doc.go predefined vs keyword
  16. 37.

    - they point to a position in memory - similar

    to C or C++, but - no pointer arithmetic → memory safety - garbage collection pointers in Go
  17. 41.

    a slice with five elements s := make([]byte, 5) 5

    5 0 0 0 0 0 []byte [5]byte ptr len cap
  18. 51.

    var p *Person // nil of type *Person var s

    fmt.Stringer = p // Stringer (*Person, nil) fmt.Println(s == nil) // false
  19. 54.

    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
  20. 56.

    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
  21. 57.

    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
  22. 60.

    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
  23. 64.

    var p *int p == nil // true *p //

    panic: invalid memory address or nil pointer dereference pointers
  24. 66.

    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
  25. 67.

    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
  26. 68.

    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
  27. 69.

    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
  28. 71.

    func (t *tree) Sum() int { if t == nil

    { return 0 } return t.v + t.l.Sum() + t.r.Sum() } nil receivers are useful: Sum
  29. 72.

    func (t *tree) String() string { if t == nil

    { return "" } return fmt.Sprint(t.l, t.v, t.r) } nil receivers are useful: String
  30. 73.

    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
  31. 76.

    var s []slice len(s) // 0 cap(s) // 0 for

    range s // iterates zero times s[i] // panic: index out of range nil slices
  32. 77.

    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
  33. 78.

    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
  34. 81.

    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
  35. 82.

    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 }
  36. 92.

    var c chan t <- c // blocks forever c

    <- x // blocks forever close(c) // panic: close of nil channel nil channels
  37. 95.

    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. 96.

    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
  39. 97.

    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
  40. 98.

    func merge(out chan<- int, a, b <-chan int) { for

    { select { case v := <-a: out <- v case v := <-b: out <- v } } } a naive solution
  41. 99.

    checking for closed chans case v, ok := <-a: if

    !ok { aClosed = true continue } out <- v // analogous code for case b
  42. 100.

    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 } } }
  43. 101.

    $ 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
  44. 102.

    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) }
  45. 103.

    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) }
  46. 104.

    1 1 2 1 1 2 2 1 1 2

    1 2 2 1 2 1 2 1 2 2 $ go run closingOut.go
  47. 107.

    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
  48. 108.

    $ 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
  49. 109.

    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
  50. 111.

    var c chan t <- c // blocks forever c

    <- x // blocks forever close(c) // panic: close of nil channel nil channels
  51. 112.

    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
  52. 113.

    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
  53. 114.

    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
  54. 115.

    1 1 2 1 1 2 2 1 1 2

    1 2 2 1 2 1 2 1 2 2 $ go run closingOut.go
  55. 119.

    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 }
  56. 120.

    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
  57. 122.
  58. 125.
  59. 126.

    type ints []int func (i ints) Sum() int { s

    := 0 for _, v := range i { s += v } return s } summer
  60. 127.
  61. 129.

    func doSum(s Summer) int { if s == nil {

    return 0 } return s.Sum() } nil values and default values
  62. 130.

    nil values and default values var t *tree doSum(t) //

    (*tree, nil) var i ints doSum(i) // (ints, nil) doSum(nil) // (nil, nil)
  63. 133.
  64. 135.

    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