When a Golang nil error is not nil
Coding in Golang are usually fun and easy for developers that are new to the language. There are sometimes gotcha which will catch out even experience developers of the language. One of the most intriguing gotchas I have recently encountered at work is that an error checking of a nil error ends up being non-nil.
Take for example you want to create a custom error object that adds more details to an error.
type CustomError struct {
err error
errorCode int
}
func (c *CustomError) Error() string {
return c.err.Error()
}
Now you have an inner function that returns your custom error.
func innerFunction() *CustomError {
// Do something
return nil
}
Your outer function conveniently uses a helper function to print and handle the error if it is not nil
func printErrorIfNotNil(err error) {
if err != nil {
fmt.Printf("Error found : %v", err)
}
}
func outerFunction() {
err := innerFunction()
printErrorIfNotNil(err)
}
So far everything looks good and logical, right?
Let’s run the full code and you will expect to see no errors printed.
Run it on go playground
package main
import "fmt"
type CustomError struct {
err error
errorCode int
}
func (c *CustomError) Error() string {
return c.err.Error()
}
func innerFunction() *CustomError {
// Do something
return nil
}
func printErrorIfNotNil(err error) {
if err != nil {
fmt.Printf("Error found : %v", err)
}
}
func outerFunction() {
err := innerFunction()
printErrorIfNotNil(err)
}
func main() {
outerFunction()
}
Result:
Error found : <nil>
Program exited.
What? Error found but error is <nil>
? What sorcery is this?!
This gotcha is clearly explained in the official Golang doc.
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.
In simpler terms let’s look at our example. In innerFunction()
, we return nil on a function that returns *CustomError
. This means assigning nil value to *CustomError
. We use this as an argument to a function that takes in error
interface. Now this means we are storing a nil pointer of type *CustomError
inside an interface value, which means this interface is no longer nil regardless of what value is the pointer.
To prevent this gotcha, I strongly recommend to always return error
type whenever you design your function API signature instead of custom error types. This improves interoperability between your code and also consumers of your packages and eliminates ambiguity.
func innerFunction() error {
// Do something
return nil
}
Check out the working code Run it on go playground
package main
import "fmt"
type CustomError struct {
err error
errorCode int
}
func (c *CustomError) Error() string {
return c.err.Error()
}
func innerFunction() error {
// Do something
return nil
}
func printErrorIfNotNil(err error) {
if err != nil {
fmt.Printf("Error found : %v", err)
}
}
func outerFunction() {
printErrorIfNotNil(innerFunction())
}
func main() {
outerFunction()
}
Another interesting way to use custom errors is via sentinel errors in Golang. Sentinel error are unaffected by this gotcha as we are creating a custom errors using constant instead of pointers. You can embed additional information as part of your sentinel errors by wrapping them and returning them as error
type.
You May Also Like
Crash Course on Golang Benchmarks: A Beginner's Perspective
Golang, renowned for its simplicity and efficiency, employs benchmark testing as a fundamental tool for performance evaluation. In this exploration, …
Read ArticleAutomatic Dark Mode in Hugo Based on User System
In our previous article, we delved into how we can add dark mode using Hugo. If you missed that insightful read, it is best to catch up before …
Read Article