- interfaceを実装している構造体を参照で返すfuncを作る
- そのfuncからnilを返す
- 実装している構造体参照型として受け取ると、正常にnil判定される
- interface型で受け取ると、nil判定がうまく行かず、使うともちろんpanicになる
自前でerrorを作ろうとしてたときにハマりました。interfaceに対する理解が足りないのか、バグなのか。。。
interface型は、typeとvalueを持っていて、両方nilでないとnil判定されない
@yosssiさんにコメントで教えていただきました。ありがとうございます。
interface型は、内部的にtype, valueを持っており、nilと判定されるには、そのどちらもnilでないといけないそうです。
https://golang.org/doc/faq#nil_error での説明 "An interface value is nil only if the inner value and type are both unset, (nil, nil)." によりますと、interface型の変数がnilと判定されるには、その内部の値と型の両方がnilである必要があるみたいですね。
今回の例の場合は、errの内部の値がnilである一方で、型がnilでない(*MyErrorである)ために、nilと判定されていないようです。
今回のケースでは、受け取る方が*MyError
の時は、自前の構造体型なので、そのままnilが入り、ちゃんとnil判定されています。
受け取る方が、errorの場合は、interface型なので、上記に該当し、type=*MyError, value=nilという状態になり、nil判定が想定通りにいかないということです。
いただいたコメント内のGo Playgroundでreflectを使って確認されているのでわかりやすいと思います。
また、半年前ぐらいに書かれていた絶対ハマる、不思議なnilにも、詳しく載っていました。
上記のコメントにある通り、明示的にnilを返すというのが大事そうです。
私の場合、最初に変数を宣言して、最後にreturnするやり方は、コードの見通しが良くないと思っているので、エラーがない時はreturn nil
といった風に返しています。ただ、複数階層funcを呼ぶコードを書いていて、以下のような直接return OtherFunc()
をしている箇所があり、これが、type=*MyError, value=nilという状況を発生させていました。
func TopFunc() error {
if something {
// ここで、*MyError型のnilがerrorとして返されてハマる。
return OtherFunc()
} else {
return nil
}
}
func OtherFunc() *MyError {
if something {
return nil
} else {
return &MyError{}
}
}
環境
環境は、golang 1.3.3/MacOS 10.9.5です。
以下のGo Playgroundでも発生します。
http://play.golang.org/p/Jho3HHYTHy
package main
import (
"fmt"
)
func NoError() error {
return nil
}
type MyError struct {
MyMsg string
}
func (e MyError) Error() string {
return e.MyMsg
}
func MyErrorRef() *MyError {
return nil
}
func main() {
var err error
err = nil
if err != nil {
fmt.Printf("1. this message does not show.\n")
}
err = NoError()
if err != nil {
fmt.Printf("2. this message does not show.\n")
}
myErr := MyErrorRef()
if myErr != nil {
fmt.Printf("3. this message does not show.\n")
}
err = MyErrorRef()
fmt.Printf("4. err: %v, nil: %v\n", err, nil)
if err != nil {
fmt.Printf("5. err is nil, but 'err != nil' is true...? will be panic.\n")
fmt.Printf("6. %s\n", err.Error())
} else {
fmt.Printf("7. why doesn't show this message?\n")
}
}