インターフェース値のnilを扱うということ
インターフェース型
Golangのインターフェース型は、具象型がインターフェースのインスタンスとして見なされるために必要なメソッドの集まりを定義します。例えば標準パッケージのioに含まれる以下のReaderやWriterはインターフェース型です。
package io type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) }
インターフェース値
インターフェース値はインターフェース型の値です。インターフェース値は以下のようなデータ構造をとります。
research!rsc: Go Data Structures: Interfaces より引用
概念としてインターフェース値はtypeとvalueから構成されます。インターフェースのゼロ値はtypeとvalueがともにnilです。
インターフェース値がnilであるということは?
typeがnilでないがvalueがnilであるインターフェース値と、typeもvalueもnilインターフェースであるインターフェース値は異なります。
公式のドキュメント https://golang.org/doc/faq#nil_error にその内容があります。
type myError struct { message string } func (e *myError) Error() string { if e == nil { return "myError: <nil>" } return "myError: " + e.message } func returnsError() error { var p *myError = nil if isDebug { p = &myError{"myError occured"} } return p // Will always return a non-nil error. }
上記の実装は常に error インターフェースは nil になりません。
reflect で 型情報を出力すると明らかです。
package main import ( "fmt" "reflect" ) var isDebug = false type myError struct { message string } func (e *myError) Error() string { if e == nil { return "myError: <nil>" } return "myError: " + e.message } func returnsError() error { var p *myError if isDebug { p = &myError{message: "myError occured"} } return p // Will always return a non-nil error. } func main() { err := returnsError() if err != nil { fmt.Println("err is NOT <nil>") } else { fmt.Println("err is <nil>") } // err is NOT <nil> fmt.Println("----------------------------") fmt.Println("is nil:", err == nil) fmt.Println("Type:", reflect.TypeOf(err)) fmt.Println("Value:", reflect.ValueOf(err)) // err is <nil> fmt.Println("----------------------------") var nilErr error fmt.Println("is nil:", nilErr == nil) fmt.Println("Type:", reflect.TypeOf(nilErr)) fmt.Println("Value:", reflect.ValueOf(nilErr)) }
https://play.golang.org/p/21Cj7MKOiLV
err is NOT <nil> ---------------------------- is nil: false Type: *main.myError Value: myError: <nil> ---------------------------- is nil: true Type: <nil> Value: <invalid reflect.Value>
ということで正しい実装例は以下になります。
Bad | Good |
---|---|
func returnsError() error { var p *myError = nil if isDebug { p = &myError{"myError occured"} } return p // Will always return a non-nil error. } |
func returnsError() error { if isDebug { return &myError{"myError occured"} } return nil } |