Nil vs Null

go nil is not the same as java null

In Java the most “popular” error is java.lang.NullPointerException. The problem is so pervasive, so bad, that there are even things like java.util.Optional and com.google.common.base.Optional. The purpose (insofar as I understand it) is to get callers of methods to actually check if something was returned by a method call.

The problem is that every object variable (any variable that is not a for a primitive type) in Java is actually a reference, that is, a pointer. Every object variable can contain null, and you have to work to make sure there is a value there. Needless to say, often there is nothing there, except null.

Also, there are many, many, many cases where methods cannot find a suitable thing to return, and need to return null to indicate nothing was found. (This is what the Optionals were created for.)

In java there is no direct syntax for a pointer type, but pointers are implicit everywhere. Consequently, there are a lot of unitialized pointers, and a lot of errors caused by trying to dereference those pointers.

In Go, it’s a very different story. Go adds explicit pointer types, and has nil as the “not pointing anywhere” value. If you try to use such a pointer in go, you’ll get

panic: runtime error: invalid memory address or nil pointer dereference

It’s tempting to say that nil is the same as Java’s null, but this leads to the impression that you’ll end up with just as many “nil pointer dereference” errors in Go as you get NPEs in Java.

But it ain’t so.

Why is Go different?

There are few reasons that programming (idiomaticly) in Go will yield far, far fewer exceptions from bad pointers.

  1. explicit pointers & zero values
  2. multiple return values
  3. always checking for errors
  4. methods can be invoked on nil pointers

zero values

In Go, the analog of a class is a struct. The default case of dealing with structs is actually not to have a pointer, but just have a struct.

type Member struct {
    Name string
    Email string
    Phone string
}

var aMember Member

In this example, we have a variable, aMember which is an instance of a Member. The important difference between this and an analagous Java variable is that it is already initialized, and has values (blank strings, in this case), and methods can be invoked on it.

Clearly, you can still have bugs from unitialized variables, but it’s not going to cause your program to panic.

The point here is that if you don’t have a real need for a pointer, you can just use plain struct variable and you are guaranteed that it will have valid zero values (“” for strings, 0 for numbers, false for bool, etc.).

explicit pointers, multiple return values

Of course, you can have pointers, when you want, and can pass pointers around rather than copies of structs. Also, you can have methods that return pointers. And methods that return multiple values.

func findMemberByName(name string) (member *Member, found bool) {
    ... blah blah ...
    if finderTool.NotFound() {
        return nil, false
    }
    return &foundMember, true
}

...

memb, found := findMemberByName("george")
if !found {
    // handle it
}

This is an example of how this kind of situation is generally handled. If you have a function/method that might or might not return a value, the standard is to return two values: the actual thing, and a flag indicating success/failure.

In this example, if we did not have the if !found... check, we’d likely get an error about having an unused variable. Yes, it’s actually an error in Go to have an unused variable.

While it’s permitted to explicitly ignore the returned flag

memb, _ := findMemberByName("george")

it’s pretty obvious you’re living life on the edge.

The standard Go pattern is to immediately check the values returned from a method and to handle exceptional conditions right there. There is no expectation (and not really any acceptable mechanism) to handle exceptions later. Handle it now.

nil receivers

func (name *Name) sayHello() {
    if name == nil {
        fmt.Printf("hello, world\n")
        return
    }
    fmt.Printf("hello, %s\n", *name)
}

The above method is defined on a pointer type. It’s totally reasonable, in Go, to invoke a method on a pointer variable which has a nil value. As shown, it’s trivial to check the value of the receiver variable (here named name) for nil and handle the situation gracefully. (Run it in the playground)

conclusion

Of course it’s still possible to have dereferencing errors in Go, but from a combination of factors, it’s far less likely than in some other languages. Lessons learned over the years have contributed to the design of Go, which helps direct programmers to write correct programs.