型付きnilをinterfaceに入れると == nil が false になる
nilのポインタをerrorなどのinterface型の変数に代入すると、そのinterfaceは型情報を持った「型付きnil」になります。値自体はnilでも、interfaceとしてはnilと等しくなくなります。
なぜエラーが出ないのか
出力: false
(エラーなし)- Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: false- 実際の出力 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 package main 2 3 import "fmt" 4 5 type MyErr struct{} 6 7 func (e *MyErr) Error() string { return "boom" } 8 9 func check() error { 10 var e *MyErr 11 _ = e 12 return e ^ 13 } 14 15 func main() { 16 err := check() 17 fmt.Println(err == nil) 18 }
eはnilのポインタですが、それをerrorインターフェースとしてreturnすると型情報が付いた「型付きnil」になり、err == nilはfalseになります。
直し方: return e を return nil にします。
パターン2
1 package main 2 3 import "fmt" 4 5 type ValidationError struct{} 6 7 func (e *ValidationError) Error() string { return "invalid" } 8 9 func validate() error { 10 var e *ValidationError 11 _ = e 12 return e ^ 13 } 14 15 func main() { 16 err := validate() 17 fmt.Println(err == nil) 18 }
エラーが無いことを表すつもりでも、型付きの変数をそのままreturnするとinterfaceとしては非nil扱いになります。
直し方: return e を return nil にします。
パターン3
1 package main 2 3 import "fmt" 4 5 type NotFoundError struct{} 6 7 func (e *NotFoundError) Error() string { return "not found" } 8 9 func find() error { 10 var e *NotFoundError 11 _ = e 12 return e ^ 13 } 14 15 func main() { 16 err := find() 17 fmt.Println(err == nil) 18 }
interfaceは(型, 値)のペアなので、値がnilでも型情報が入っていればnilと等しくありません。
直し方: return e を return nil にします。
パターン4
1 package main 2 3 import "fmt" 4 5 type TimeoutError struct{} 6 7 func (e *TimeoutError) Error() string { return "timeout" } 8 9 func wait() error { 10 var e *TimeoutError 11 _ = e 12 return e ^ 13 } 14 15 func main() { 16 err := wait() 17 fmt.Println(err == nil) 18 }
呼び出し元でif err != nilと書いても、この型付きnilは常にtrueと判定されてしまいます。
直し方: return e を return nil にします。
パターン5
1 package main 2 3 import "fmt" 4 5 type ParseError struct{} 6 7 func (e *ParseError) Error() string { return "parse failed" } 8 9 func parse() error { 10 var e *ParseError 11 _ = e 12 return e ^ 13 } 14 15 func main() { 16 err := parse() 17 fmt.Println(err == nil) 18 }
eに何も代入していない(実体を作っていない)ことと、それをinterfaceとして返すことは別問題です。
直し方: return e を return nil にします。
よくある誤解
「中身のポインタがnilなら、interface変数を == nil で比較すればtrueになるはず」という思い込みは誤りです。interfaceは(型, 値)のペアで構成されており、値がnilでも型情報が入っていればinterface全体としてはnilと等しくありません。
まとめ
型付きnilをinterfaceに入れると == nil が false になるは中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。