Beware of Go interfaces
Tags: go, pitfall
Published: 2023-08-30 19:03:50

Interfaces play a huge part when working with the Go programming language. The implicit implementation approach that Go takes makes them very flexible and easy to write. But interfaces in Go have a dark side when dealing with `nil` comparisons and this article aims to shine some light on it.

No time to waste, let's directly dive into it. Since `error` is probably the most important and most commonly used interface in Go, it will be used in the examples that follow. However, the problem is applicable to any other interface type.

## What just happened

Let's have a look at the following code.

```go
package main

import (
	"fmt"
	"log"
)

type DivisionByZeroError struct {
	Numerator   int
	Denominator int
}

func (e *DivisionByZeroError) Error() string {
	return fmt.Sprintf("division by zero: %d / %d", e.Numerator, e.Denominator)
}

func divide(numerator, denominator int) (int, error) {
	var (
		result int
		err    *DivisionByZeroError
	)
	if denominator != 0 {
		result = numerator / denominator
	} else {
		err = &DivisionByZeroError{
			Numerator:   numerator,
			Denominator: denominator,
		}
	}
	return result, err
}

func main() {
	result, err := divide(10, 5)
	if err != nil {
		log.Fatalf("Division by zero: %v", err)
	}
	log.Printf("Answer: %d", result)
}
```

It might be a bit overengineered for a simple `divide` function but it's all for demonstration purposes.

The important thing to note is that we have defined a custom type `DivisionByZeroError` that implements the `error` interface and is returned by `divide` whenever one tries to divide by zero.

We run the program and since we divide `10` by `5` we expect to get `Answer: 2` but instead we get the following error:

```
2009/11/10 23:00:00 Division by zero: <nil>
```

NOTE: You can run the program in the [Go Playground](https://play.golang.com/p/eGdnjisybFs).

This makes absolutely no sense. Not only did Go decide that `err != nil` was `true`, when it should have been `false`, but it also printed `<nil>` to the console, contradicting itself.

What is going on? Is this a compiler error? Was the custom error type incorrectly defined?

## Simplified example

Well - no. The problem from above has to do with how Go converts from concrete types to interfaces and how it compares interfaces.

Here is a simplified example of the same problem.

```go
package main

import (
	"log"
)

type CustomError struct{}

func (e *CustomError) Error() string {
	return "custom error"
}

func main() {
	var typed *CustomError = nil
	var err error = typed
	if err != nil {
		log.Fatalf("Error: %v", err)
	}
}
```

NOTE: You can run the program in the [Go Playground](https://play.golang.com/p/zRxqJRB0kiD).

The problem occurs on the following line:

```go
var err error = typed
```

This converts the `nil` value of type `*CustomError` to an interface of type `error`. The problem is that the interface is not `nil`, despite what one may think.

It prints as `<nil>` to the console but this is only because it references a `nil` value of `*CustomError` but the interface itself is not `nil`.

## Inner workings

We have to look at how interfaces are represented internally to understand what is going on.

An interface in Go can be thought of as a struct that has two fields - a reference to the value and a reference to the type it represents.

```go
type interface struct {
  value   *internal.Pointer
  type    *internal.Type
}
```

**NOTE:** This is an over-simplified Go-syntax representation, which acts only for demonstration purposes and does not accurately reflect the actual design.

When you assign a variable to an interface, the `value` field holds a pointer to your variable and the `type` field holds a reference to the Type definition of your type. That way, Go can know what methods the value has and how to cast it, if needed.

When you compare an interface in Go with `nil`, Go checks that `value` and `type` are both `nil`. If `value` is `nil` but `type` is not, then the check will fail.

In our case, when we assign `var err error = typed`, in essence we do `var err error = (*CustomError)(nil)`.

This can be thought of as resulting in the following:

```go
var err error = interface {
  Value:  nil,
  Type:   CustomError.(type),
}
```

**NOTE:** Again, this is total nonsense Go code but it should help you get a feel for how it works internally.

As explained above, since `type` is not `nil`, the check `err == nil` returns `false`, or vice versa, the check `err != nil` returns `true`, which happens to be our case.

## But why

I have known of this "problem" for a long time now and I have long been asking myself why did the Go developers choose to implement interface comparison this way.

Why didn't they just check that the interface `value` isn't `nil` and ignore the `type`?

Only recently did I put two and two together and figured out why this was the case. Up to that point, I always thought it was for legacy / backward compatibility reasons.

The thing is, `nil` values in Go are actually usable. And since it will be hard to explain it in words, here is an example with our `CustomError` from above.

```go
package main

import (
	"log"
)

type CustomError struct{}

func (e *CustomError) Error() string {
	return "custom error"
}

func main() {
	var err *CustomError = nil
	log.Println(err.Error())
}
```

NOTE: You can run the program in the [Go Playground](https://play.golang.com/p/0nnpF93DXoP).

You might expect the program to panic, since we are calling the `Error` method on the `err` value, which is `nil`. However, instead, the program runs successfully and outputs the following to the console.

```
2009/11/10 23:00:00 custom error
```

Go allows you to call a method on a `nil` value of a type. And as long as the implementation of the method does not dereference the receiver, nothing will panic. In fact, in our particular case we can define the `Error` method of `CustomError` in this simplified form - without a receiver variable name since we are not using the receiver in any way.

```go
func (*CustomError) Error() string {
	return "custom error"
}
```

What this means is that in some cases passing a `nil` value of a type might actually be e valid / working implementation of some interface.

Hence, it would be incorrect from Go to treat the interface as `nil` (unusable), since in fact it is perfectly valid, even if there is no actual value behind it and only a type.

## Now what

Ok, so now that we know why Go compares interfaces with `nil` in such a way, what can we do to prevent the problem we had from the first example?

An easy solution is to never implicitly pass `nil` to an interface and instead always be explicit. That is, the original "divide" example would instead look as follows:

```go
package main

import (
	"fmt"
	"log"
)

type DivisionByZeroError struct {
	Numerator   int
	Denominator int
}

func (e *DivisionByZeroError) Error() string {
	return fmt.Sprintf("division by zero: %d / %d", e.Numerator, e.Denominator)
}

func divide(numerator, denominator int) (int, error) {
	if denominator == 0 {
		return 0, &DivisionByZeroError{ // explicit
			Numerator:   numerator,
			Denominator: denominator,
		}
	}
	return numerator / denominator, nil // explicit
}

func main() {
	result, err := divide(10, 5)
	if err != nil {
		log.Fatalf("Division by zero: %v", err)
	}
	log.Printf("Answer: %d", result)
}
```

NOTE: You can run the program in the [Go Playground](https://play.golang.com/p/jEKFBazhijr).

The code became much cleaner. Since this is probably how one would have written it to begin with, one might assume that it would be hard to get into the problem from the initial example. But I have seen it in practice a number of times. Often it is not so trivial and has to do with more complex functions being chained.

Most often it starts with a function that returns a concrete error type instead of the `error` interface...

```go
func doSomething() (int, *CustomError) {
  ...
}
```

...and a caller that propagates the error implicitly.

```go
func caller() error {
  result, err := doSomething()
  if err == nil {
    log.Println(result)
  }
  return err // implicit
}
```

---

Instead, the `caller` function should perform explicit error returns as follows.

```go
result, err := doSomething()
if err != nil {
  return err // explicit
}
log.Println(result)
return nil // explicit
```

And the `doSomething` function should not have returned `*CustomError` as a result parameter. Instead, if possible, it should have used the `error` interface instead.

```go
func doSomething() (int, error) {
  ...
}
```

---

Lastly, avoid named result parameters.

```go
func caller() (err error) {
  var result int
  result, err = doSomething() // implicit
  if err == nil {
    log.Println(result)
  }
  return // implicit
}
```

They tend to get people in trouble when not careful.

## Fin

If you got so far, thank you for following along. Hopefully, this article was helpful.

It does not aim to bash Go in any way. As has been shown, there is a very good reason for Go to compare interfaces in such a way. Instead, my goal is for this to be helpful to new Go developers that are still learning the language and spare them a painful debugging session.

Feel free to add details in the comments if you think there is something I got wrong or if there is something important I forgot to mention.

Happy Go coding!