understanding nil @francesc
thanks
welcome every single one of you
agenda what is nil? what is nil in Go? what does nil mean? is nil useful?
nil? you misspelled null
how I learn words
how I learn words
etymology nil Latin nihil meaning nothing null Latin ne + ullus meaning not any none Old English ne + ān meaning not one
names for the number zero in English - zero null duck nada zip naught aught - cipher love nil zilch the letter o nought ought source: wikipedia
nil is (a) zero
interlude a bit of history
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
panic: runtime error: invalid memory address or nil pointer dereference
Uncaught TypeError: undefined is not a function
nil leads to panic
panic leads to fear by danpawley on Flickr
fear leads to by korymatthew on Flickr
λ functional programming
Functional Go? Thanks, but no - francesc at dotgo.eu 2015
there are many ways, nil is the Go way
</interlude>
zero values what are they?
zero values bool false pointers nil numbers 0 slices nil string "" maps nil channels nil functions nil interfaces nil
zero values for struct types type Person struct { AgeYears int Name string Friend []Person } var p Person // Person{0, "", nil}
the type of nil
... unless the value is the predeclared identifier nil, which has no type - Go language specification
untyped zero a := false a := "" a := 0 // a := int(0) a := 0.0 // a := float64(0) a := nil // use of untyped nil
nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type. - Go documentation for builtin package
nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type. - Go documentation for builtin package
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
predefined vs keyword // YOLO var nil = errors.new( \_(ツ)_/ ) * For extra evilness: place at the end of doc.go
zero values what do they mean?
kinds of nil pointers slices maps channels functions interfaces
kinds of nil pointers slices maps channels functions interfaces
pointers in Go - they point to a position in memory - similar to C or C++, but - no pointer arithmetic memory safety - garbage collection
nil pointer - points to nil a.k.a. nothing - zero value of pointers
kinds of nil pointers slices maps channels functions interfaces
slice internals []byte ptr *elem len int cap int
a slice with five elements []byte ptr len 5 cap 5 [5]byte 0 0 0 0 0 s := make([]byte, 5)
nil slice []byte ptr nil len 0 cap 0 var s []byte
kinds of nil pointers slices maps channels functions interfaces
channels, maps, and functions ptr *something
channels, maps, and functions ptr implementation * implementations might not be cloud shaped
channels, maps, and functions ptr nil
kinds of nil pointers slices maps channels functions interfaces
(type, value)
var s fmt.stringer // Stringer (nil, nil) fmt.println(s == nil) // true
(nil, nil) equals nil
var p *Person // nil of type *Person var s fmt.stringer = p // Stringer (*Person, nil) fmt.println(s == nil) // false
(*Person, nil) doesn t equal nil
when is nil not nil?
when is nil not nil? func do() error { var err *doerror return err } // error (*doerror, nil) // nil of type *doerror func main() { err := do() fmt.println(err == nil) } // error (*doerror, nil) // false
do not declare concrete error vars
nil is not nil func do() *doerror { // nil of type *doerror return nil } func main() { err := do() fmt.println(err == nil) } // nil of type *doerror // true
nil is not nil func do() *doerror { return nil } // nil of type *doerror func wrapdo() error { return do() } // error (*doerror, nil) // nil of type *doerror func main() { err := wrapdo() fmt.println(err == nil) } // error (*doerror, nil) // false
do not return concrete error types
nil is not nil * for some kinds of nil
kinds of nil 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
Make the zero value useful - Rob Pike in his Go Proverbs
how are these useful? pointers slices maps channels functions interfaces
how are these useful? pointers slices maps channels functions interfaces
pointers var p *int p == nil // true *p // panic: invalid memory address or nil pointer dereference
coding time
implement Sum type tree struct { v int l *tree r *tree } 6 2 1 func (t *tree) Sum() int 7 4 3 9 5 8
a first solution func (t *tree) Sum() int { sum := t.v 6 if t.l!= nil { sum += t.l.sum() } if t.r!= nil { sum += t.r.sum() } return sum } 2 1 7 4 3 9 5 8
issues 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
pointer receivers type person struct {} func sayhi(p *person) { fmt.println( hi ) } func (p *person) sayhi() { fmt.println( hi ) } var p *person p.sayhi() // hi
nil receivers are useful 零
nil receivers are useful: Sum func (t *tree) Sum() int { if t == nil { return 0 } return t.v + t.l.sum() + t.r.sum() }
nil receivers are useful: String func (t *tree) String() string { if t == nil { return "" } return fmt.sprint(t.l, t.v, t.r) }
nil receivers are useful: Find func (t *tree) Find(v int) bool { if t == nil { return false } return t.v == v t.l.find(v) t.r.find(v) }
keep nil useful if possible, if not NewX()
how are these useful? pointers slices maps channels functions interfaces
nil slices var s []slice len(s) // 0 cap(s) // 0 for range s // iterates zero times s[i] // panic: index out of range
append on nil slices var s []int for i := 0; i < 10; i++ { fmt.printf("len: %2d cap: %2d\n", len(s), cap(s)) s = append(s, i) }
$ go run slices.go len: len: len: len: len: len: len: len: len: len: 0 1 2 3 4 5 6 7 8 9 cap: 0 cap: 1 cap: 2 cap: 4 cap: 4 cap: 8 cap: 8 cap: 8 cap: 8 cap: 16 [] s is nil! [0] allocation [0 1] reallocation [0 1 2] reallocation [0 1 2 3] [0 1 2 3 4] reallocation [0 1 2 3 4 5] [0 1 2 3 4 5 6] [0 1 2 3 4 5 6 7] [0 1 2 3 4 5 6 7 8] reallocation
use nil slices they re often fast enough
how are these useful? pointers slices maps channels functions interfaces
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
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 }
using maps NewGet( "http://google.com", map[string]string{ "USER_AGENT": "golang/gopher", }, )
$ go run request.go GET / HTTP/1.1 Host: google.com User_agent: golang/gopher
using empty maps NewGet("http://google.com", map[string]string{})
$ go run request.go GET / HTTP/1.1 Host: google.com
using empty maps NewGet("http://google.com", map[string]string{})
nil maps are valid empty maps NewGet("http://google.com", nil)
$ go run request.go GET / HTTP/1.1 Host: google.com
use nil maps as read-only empty maps
how are these useful? pointers slices maps channels functions interfaces
nil channels var c chan t <- c // blocks forever c <- x // blocks forever close(c) // panic: close of nil channel
coding time
b a func merge(out chan<- int, a, b <-chan int) out
a naive solution func merge(out chan<- int, a, b <-chan int) { for { select { case v := <-a: out <- v case v := <-b: out <- v } } }
$ go run naive.go 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
closed channels var c chan t v, ok <- c // zero(t), false c <- x // panic: send on closed channel close(c) // panic: close of nil channel
a naive solution func merge(out chan<- int, a, b <-chan int) { for { select { case v := <-a: out <- v case v := <-b: out <- v } } }
checking for closed chans case v, ok := <-a: if!ok { aclosed = true continue } out <- v // analogous code for case b
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 } } }
$ 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
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) }
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) }
$ go run closingout.go 1 1 2 1 1 2 2 1 1 2 1 2 2 1 2 1 2 1 2 2
ship it! Fancy Gopher by Renee French
by hzeller on Flickr
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
$ 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
let s log 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)
can we switch off a chan?
nil channels var c chan t <- c // blocks forever c <- x // blocks forever close(c) // panic: close of nil channel
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
switching off a channel 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; no logs 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) }
$ go run closingout.go 1 1 2 1 1 2 2 1 1 2 1 2 2 1 2 1 2 1 2 2
fancy party time! Fancy Gopher by Renee French
use nil chans to disable a select case
how are these useful? pointers slices maps channels functions interfaces
nil funcs Go has first-class functions functions can be used as struct fields they need a zero value; logically it is nil type Foo struct { f func() error }
nil funcs for default values 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")) }
how are these useful? pointers slices maps channels functions interfaces
interfaces The nil interface is used as a signal if err!= nil { }
why does nil *Person not equal nil interface? 零
summer type Summer interface { func Sum() int }
summer var t *tree var s Summer = t fmt.println(t == nil, s.sum()) // true 0
summer type ints []int func (i ints) Sum() int { s := 0 for _, v := range i { s += v } return s }
summer var i ints var s Summer = i fmt.println(i == nil, s.sum()) // true 0
nil values can satisfy interfaces 零
nil values and default values func dosum(s Summer) int { if s == nil { return 0 } return s.sum() }
nil values and default values var t *tree dosum(t) // (*tree, nil) var i ints dosum(i) // (ints, nil) dosum(nil) // (nil, nil)
nil values and default values http.listenandserve("localhost:8080", nil)
use nil interfaces to signal default 零
nil is useful
nil is useful 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 an important part Go 零
let s not avoid nil
let s embrace nil Thanks, @francesc golang.org/s/nil