Thoughts on Whatnot

A blog about .

Interfaces and nil in Go

The oddity that nil interfaces can be in Go is one that has caught a number of people off guard. After explaining it multiple times on Reddit and elsewhere, I figured it would be a good idea to have an article I can refer to. Since I’m attempting to start a little blog here, I thought this might make a good first article.

The problem is pretty simple. It’s very possible to have what appears to be a nil interface in Go which is, in fact, not nil. For example, consider the following code:

func someFunction() error {
	// ...
}

func example() *someErrorType {
	return nil
}

func main() {
	err := someFunction()
	// err is now type error due to type inference rules.

	// ...

	err = example()
	if err != nil {
		panic(err.Error())
	}
}

Although it might not look like it at first, this code will panic. ‘But why?’, you ask. After all, example() clearly returns nil, so why would the condition be true?

The reason has to do with the way interfaces are constructed internally. Each runtime interface value is actually a double word. The first word represents the underlying type, and the second is a pointer to the underlying value. In a certain way, Go interfaces are actually closer to C’s void * than they are to Java or C# interfaces, despite the semantics. The biggest difference is that they’re type-safe due to that extra word with type information.

In a nil interface, both of these words are nil. You could think of it as a tuple looking something like (nil, nil). However, in the above example, type inference dictates that example() must be returning a *someErrorType. nil has no associated type information either, which doesn’t help. This means that Go isn’t trying to store a nil error in err, but a nil *someErrorType. So, rather than err being (nil, nil), it becomes something more like (*someErrorType, nil). And, since that’s not actually a nil interface value, the condition is true, and the code panics.

The Go idiom of ‘Accept interfaces; return values.’ is generally correct. It leads to more maintainable, flexible code. However, it can also lead to issues like the above, and the above is one of several reasons that error returns are an exception to the rule. In any code that makes heavy use of interface values, and really any code, I suppose, it’s always good to keep in mind what’s actually going behind the scenes.


Share

comments powered by Disqus