突然ですが問題です。
以下のコードを実行した時、"This should not be called"の文字列は出力されるでしょうか?
package main
import "fmt"
type MyError struct {
}
// implements error interface
func (MyError) Error() string {
return "This is MyError"
}
func fn() (int, *MyError) {
return 42, nil
}
func main() {
result, err := fn()
if err != nil {
fmt.Println("This should not be called")
}
fmt.Println(result, err)
}
実行結果
42 <nil>
出てません。fn()
はエラーnilを返しているので、if err != nil
の中には入りません。
ここまではOKですね。
では次の例ではどうでしょうか。
package main
import "fmt"
type MyError struct {
}
// implements error interface
func (MyError) Error() string {
return "This is MyError"
}
func fn() (int, *MyError) {
return 42, nil
}
func main() {
var err error
result, err := fn()
if err != nil {
fmt.Println("This should not be called")
}
fmt.Println(result, err)
}
func main()
の次の行に1行追加されているだけですが、これはどうなるでしょう?
実行結果
This should not be called
42 <nil>
なんと!"This should not be called"の文字列が出力されてしまいました!
ということはerr != nil
はtrue
ということになります。
でもちょっとまってください。その下の出力内容は42 <nil>
です。ということはerr
はやっぱりnil
なのです。だから、
if err != nil {
fmt.Printf("Error %s", err.Error())
}
のようなことをしていると、直前でnilチェックをしているにも関わらずnil参照エラーでクラッシュします。
私はこの現象に数時間悩まされました。
この例で言うところのfn()
が返却するエラーの型を*MyError
ではなくerror
とすると、この問題は発生しません。
package main
import "fmt"
type MyError struct {
}
// implements error interface
func (MyError) Error() string {
return "This is MyError"
}
func fn() (int, error) { // *MyErrorではなくerror
return 42, nil
}
func main() {
var err error
result, err := fn()
if err != nil {
fmt.Println("This should not be called")
}
fmt.Println(result, err)
}
実行結果
42 <nil>
Goの型システムの深いところまでまだ理解できていないのですが、とりあえず各関数が返却するエラーの型を*MyError
のような独自型ではなく、error
とするのが良さそうです。
同じハマり方をされていた先人たち