Golang Pointer Receivers and Error Pointer Returns

Flaviu Vadan
Vendasta
Published in
2 min readMay 22, 2019

Recently, I added a GetMulti endpoint with the following header:

The definition is problematic because of the error pointer return. When testing the new endpoint, it threw an error (returned as a: *ServiceError), which was fine and should have been handled graciously by propagating the error further. By propagating the error, the work of intercepting and converting to a final gRPC status code would be delegated to an error interceptor. However, due to the nature of how Golang handles pointer dereferencing, propagating the error caused a panic whenever the endpoint returned an error.

The error interceptor uses the following translation function:

Notice it takes in an error of type error but, a *ServiceError was returned by the endpoint. In this case, *ServiceError satisfies the error interface. However, a nil *ServiceError is not equal to a nil error, which caused FromError to perform the err.Error() return.

The following example illustrates the difference:

Uncommenting the last line results in:

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

Taken from Golang FAQs:

Under the covers, interfaces are implemented as two elements, 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.

As a result, I ended up refactoring all methods that were returning the concrete type *ServiceError to return error type errors. Finally, to quote a colleague:

The separation of the pointer to the definition of the interface and the pointer to the implementing struct underlying it is a pretty important thing to learn in Golang. I know it’s been a “lightbulb” moment for a lot of people when learning the language.

It definitely was for me.

Additional resources on the topic:

--

--