Interfaces and error checking golang
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:
doSomething1
doSomething2
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
true
thenerr != nil
and the behavior looks correct. - When the input is
false
thenerr==nil
and the behavior looks correct
Let's inspect the output of doSomething2
:
- When the input is
true
thenerr == nil
and the behavior looks wrong. - When the input is
false
thenerr!=nil
and 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
(err
variable) whenv
is false we:- then we initialize a MyErr{} and assign it's address to err
- we return the err
when
v
is 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.