Code Fix

中級

型付き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  }
出力: falseeはnilのまま代入されない

eはnilのポインタですが、それをerrorインターフェースとしてreturnすると型情報が付いた「型付きnil」になり、err == nilはfalseになります。

直し方: return ereturn 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  }
出力: false

エラーが無いことを表すつもりでも、型付きの変数をそのままreturnするとinterfaceとしては非nil扱いになります。

直し方: return ereturn 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  }
出力: false

interfaceは(型, 値)のペアなので、値がnilでも型情報が入っていればnilと等しくありません。

直し方: return ereturn 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  }
出力: false

呼び出し元でif err != nilと書いても、この型付きnilは常にtrueと判定されてしまいます。

直し方: return ereturn 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  }
出力: false

eに何も代入していない(実体を作っていない)ことと、それをinterfaceとして返すことは別問題です。

直し方: return ereturn nil にします。

この問題を解いてみる →

よくある誤解

「中身のポインタがnilなら、interface変数を == nil で比較すればtrueになるはず」という思い込みは誤りです。interfaceは(型, 値)のペアで構成されており、値がnilでも型情報が入っていればinterface全体としてはnilと等しくありません。

まとめ

型付きnilをinterfaceに入れると == nil が false になるは中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)