Be careful about nil check on interface in GoLang

  sonic0002        2019-04-06 07:47:07       16,957        1         

nil check is frequently seen in GoLang code especially for error check since GoLang's special error handling convention. In most cases, nil check is straight forward, but in interface case, it's a bit different and special care needs to be taken.

Take a look at below code snippet and guess what the output will be.

package main

import (
	"bytes"
	"fmt"
	"io"
)

func check(w io.Writer) {
	if w != nil {
		fmt.Println("w is not nil")
	}

	fmt.Printf("w is %+v\n", w)
}

func main() {
	var b *bytes.Buffer

	check(b)

	fmt.Printf("b is %+v", b)
}

The output will be:

w is not nil
w is 
b is 

In the check() method, you would expect that w would be nil in the case. But actually it's not. And when printing the object, it becomes nil. How can this happen?

The reason is that interface has a special implementation which it contains two components: a type and a value. Under the cover, interfaces are implemented as a type T and a value V. V is a concrete value such as an int, struct or pointer, never an interface itself, and has type T. For instance, if we store the int value 3 in an interface, the resulting interface value has, schematically, (T=int, V=3). The value V is also known as the interface's dynamic value, since a given interface variable might hold different values V (and corresponding types T) during the execution of the program.

An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type. If we store a nil pointer of type *int inside an interface value, the inner type will be *int regardless of the value of the pointer: (T=*int, V=nil). Such an interface value will therefore be non-nil even when the pointer value V inside is nil.

So in the above case, when creating the variable b, it has a type of *bytes.Buffer but its value is nil. Hence you would see above output. Let's see a more specific example on when an interface would be nil.

package main

import (
	"fmt"
)

type SomeError struct{}

func (se *SomeError) Error() string {
	return "error"
}

func check(e error) {
	if e == nil {
		fmt.Println("e is nil")
	}

	fmt.Printf("e is %+v\n", e)
}

func main() {
	var e error = nil
	check(e)

	var se *SomeError = nil
	check(se)
}

When creating variable e, it's an error but with no specific error type. And it has a value of nil. Hence the comparison of it to nil will return true. 

e is nil
e is <nil>
e is error

Be very careful when using interface as function parameter and doing nil check, it may not return what you expect. 

INTERFACE  GOLANG  NIL CHECK  NIL TYPE  NIL VALUE 

       

  RELATED


  1 COMMENT


Nicolás Parada [Reply]@ 2020-10-31 12:24:40

Thank you for the explanation.

The other day I was on a code interview and I failed on this tricky question. Then I was wondering how is that we always compare `err != nil` being error an interface too.

Great to know now. Tho I don't thing I've ever found this issue on real code.



  RANDOM FUN

How functional testing works