Interfaces and error checking golang
I am a software engineer based in Cyprus with over 20 years of experience in the industry. My background in Computer Science has led me to work with PHP, Python, and more recently, with a focus on Golang.
Originally from Greece, my career has taken me across Europe, and I now call Cyprus home. I've attended numerous conferences, continually expanding my knowledge and network.
Recently, I started blogging to share my insights and experiences with the tech community. I'm passionate about engaging with fellow developers and contributing to the field through my writing and future projects.
Thank you for visiting my blog.
Introduction
In this post I would like to discuss a mistake that I have seen while I was reviewing a pull request. We are going to talk a bit about golang interfaces in the context of error handling and checking if an error is nil
Let's see some code
first we define a custom error MyError and we implement the error interface.
we define 2 functions:
doSomething1doSomething2
the intention of both functions is to return an error if the input value is false
Download the code and run it via go run main.go
Here is the output:
Running doSomething1(true)
no error: <nil> <nil>
Running doSomething1(false)
error: error happened main.MyError
Running doSomething2(true)
error: <nil> *main.MyError
Running doSomething2(false)
error: error happened *main.MyError
Let's inspect the output of doSomething1:
- When the input is
truethenerr != niland the behavior looks correct. - When the input is
falsethenerr==niland the behavior looks correct
Let's inspect the output of doSomething2:
- When the input is
truethenerr == niland the behavior looks wrong. - When the input is
falsethenerr!=niland the behavior looks correct
So something is wrong with the implementation of doSomething2.
Let's investigate a bit
func doSomething2(v bool) error {
var err *MyError
if !v {
err = &MyError{}
return err
}
return err
}
doSomething2 returns type error interface and MyError implements thats.
- we declare first a pointer to
MyError(errvariable) whenvis false we:- then we initialize a MyErr{} and assign it's address to err
- we return the err
when
vis true: - we return the err
What's the problem here?
Let's inspect the output of the main.go for the executions of the doSomething2 function.
When v == true:
Running doSomething2(true)
error: <nil> *main.MyError
As you see it prints the path that is for when err != nil. But since we returned an uninitialized pointer of *MyError it should be nil, shouldn't be ?
When v == false
Running doSomething2(false)
error: error happened *main.MyError
we print the path that is for when err != nil which is correct.
Our code does not work as we expect.
Let's see why doSomething2 is wrong
The function returns an error interface .
In our main function we check if the return value of the function is nil or not. Since the function returns an interface we check if an interface is nil or not .
An interface consists of two parts:
- it's type
- it's value
there is:
type iface struct {
tab *itab
data unsafe.Pointer
}
tab: describes the interface
data: it's a pointer to the value that implements the interface
An interface is nil when both type (tab) and data (data) are nil .
You can clearly see that this is not the case in our output:
<nil> *main.MyError
As you see the data here are nil but the type is*main.MyError.
Now we have an explanation of why we see this behavior.
Let's see how we fix now doSomething2 .
func doSomething2(v bool) error {
var err error
if !v {
err = &MyError{}
return err
}
return err
}
We just need to use a declare a variable of type error and use that.
We can also do something else in our main.go if we don't want to change our doSomething2 function
if err := doSomething2(true); err.(*MyError) != nil {
...
}
we convert error to the underlying type (*MyError) and we can check it's value.
Do not do this! Instead always use the builtin error type to store the error values.
Summary
An interface consists of it's underlying type and a pointer to the value that implements they interface. An interface is nil only if both the type and the value are nil. Be aware of that and when you return interfaces and check for nil . Especially in error handling use always the built in error type to store your error and return also the builtin error type.



