Reputation: 1944
The go FQA states:
It's a good idea for functions that return errors always to use the error type in their signature (as we did above) rather than a concrete type such as *MyError, to help guarantee the error is created correctly. As an example, os.Open returns an error even though, if not nil, it's always of concrete type *os.PathError.
But this article claims that sometimes it is OK to return with a concrete type:
(It's usually a mistake to pass back the concrete type of an error rather than error, for reasons discussed in the Go FAQ, but it's the right thing to do here because
ServeHTTP
is the only place that sees the value and uses its contents.)
My question is, what are the benefits of returning with error
rather than the concrete type, how does it help guarantee that the error is created correctly, and why is it okay to return the concrete type when the error is not used in different places?
Upvotes: 5
Views: 2436
Reputation: 24776
The reason is explained by Francesc Campoy in it's GophenCon 2016 - Understanding nil lecture.
Particularly at the sections when is nil not nil and do not declare concrete error vars.
The point he makes is that although you could use a concrete error type as the return type, and it will work as expected. But it would break unexpectedly if later you or another developer convert that to an error
interface. Then suddendly the err == nil
check will give the "wrong result" since interfaces are only considered nil
unless both it's type and value are nil.
So you could start with a concrete error type like this (bad idea):
func do() *doError { // concrete error type as return value
return nil // nil of type *doError
}
func main() {
err := do() //nil of type *doError
fmt.Println(err == nil) // true
}
If later you or someone else introduce a new function wrapDo
which returns an error
interface instead of the concrete type *doType
:
func do() *doError { //
return nil // nil of type *doError
}
func wrapDo() error { // this return error interface not concrete type
return do() // nil of type *doError gets converted to (*doError, nil)
}
func main() {
err := wrapDo() // error interface type (*doError, nil)
fmt.Println(err == nil) // false!!!, surprise, surprise
}
As you can see above, the nil
of type *doError
gets converted to error
interface, with type *doError
and value nil
and that is not nil*, both the type and values need to be nil to be considered nil.
Upvotes: 2
Reputation: 51542
This is related to error
being an interface. An interface contains a pointer to the value it contains and the type of that value. An interface is nil only if both of those values are nil. So, if you return a concrete error type from a function, and then return an error
, that error will not be nil.
type MyError string
func (e MyError) Error() string {return string(e)}
func f() *MyError {
return nil
}
func g() error {
return f()
}
func main() {
x:=g()
if x==nil {
fmt.Println("nil")
}
}
In the example above, even though *MyError
value is nil, the return value of g()
is not, because it contains an interface with type *MyError
and value nil.
This is a problem for all functions returning an interface value, and most commonly observed with error
types. So do not declare functions returning concrete error values unless those functions have very limited use, such as unexported functions.
Upvotes: 4