The Gopher character is based on the Go mascot designed by Renee French.
始めに
TIG DXUnitの宮崎です。
2021/8/16にGo1.17がリリースされましたね。
Go 1.17連載第6回目ということで、Go Vetによる静的解析が強化され、error
を実装した構造体に対するIs/As/Unwrap
のシグネチャチェックが実施されるようになったという小ネタを紹介します。
errors.Is/As/Unwrap
に関してはGo Tips連載6: Error wrappingされた各クラウドSDKの独自型エラーを扱う記事で復習もできますので、なんだっけ? という方は参照いただけるとです。
なお、この記事では以下の表記ルールとしています。
errors
パッケージのIs/As/Unwrap
メソッド
=errors.Is/As/Unwrap
error
インタフェースを実装した構造体のIs/As/Unwrap
メソッド
=error#Is/As/Unwrap
TL;DR
error
インタフェースを実装し、かつIs/As/Unwrap
メソッドを実装する場合、以下シグネチャ以外は警告されるようになった。Is(error) bool
As(interface{}) bool
Unwrap() error
- あくまで警告であり、ビルドも実行も可能
- 挙動にも変更点はなし
error#Is/As/Unwrap
is 何
Go1.17のリリースノートを引用すると以下の通り。
The vet tool now warns about methods named As, Is or Unwrap on types implementing the error interface that have a different signature than the one expected by the errors package. |
error
インタフェースを実装した時にIs/As/Unwrap
のシグネチャが間違ってた時に怒ってくれるようになったらしいです。Is/As/Unwrap
をなんとなーくしか捉えていなかったので、これを期にしっかり学んでみます。
そもそもerror
Goのerror
インタフェースについての復習から。ご存知の通り、Goにはtry/catch
構文が存在しなく、error
インタフェースを実装した構造体を返却することで例外発生を表現します。error
インタフェースとはGoに組込まれているインタフェースで、具体的には以下の通り定義されています。
type error interface { |
Error()
というメソッドを実装してstring
を返せばなんでもerror
になれるということですね。シンプルですが、これだけではエラーとしての表現力が乏しく、実際使う場合は具体的にどの型のエラーなのか、どの型が発生源なのかを判別して挙動を分岐させたりします。
errors.Is/As/Unwrap
はGo 1.13で導入されたerror
の階層化や型比較を実現するためのメソッドです。
※error
に関する記述はソースを見るのが一番早いです。
errors.Unwrap
/error#Unwrap
Go 1.13にて以下が追加されerror
の階層化ができるようになりました。
errors.Unwrap
fmt.Errorf()
に、%w
識別子が追加
fmt.Errorf()
で階層化させてUnwrap
で取り出すという流れですね。errors.Unwrap
の実装は下記の通り。Unwrap() error
を実装しない構造体の場合はnil
が返却されるようになってます。階層化を実現するための重要なメソッドなのにシグネチャ違いで実装されていると予期した通りに動かないので、Vetが気を効かせてくれるようになったみたいですね。
func Unwrap(err error) error { |
errors.Is
/error#Is
errors.Is
は特定のエラーとの比較を、再起的に階層を掘って実施してくれます。
実装は以下の通り。
func Is(err, target error) bool { |
やっていることは↓の通り。
- targetが比較可能なら比較
err
にIs(err error) bool
メソッドが実装されていればcall- errをUnwrapする。できなければfalse返却
Unwrap
のときと同じ具合で、シグネチャを確認するような実装になっていますね。
errors.As
/error#As
errors.As
はエラーに対する型アサーションを実施してくれます。
実装は以下の通り。
func As(err error, target interface{}) bool { |
やっていることは以下です。
- targetが有効なポインタでなければエラー(errの型アサーション結果代入先であるため)
- ループ
- targetにerrが代入可能なら代入して終了
As(interface{}) bool
メソッドがあればそれを呼ぶ。trueが帰ってくれば終了- Unwrapして次ループに入る
指定した型として扱えるまで階層を掘って試行してくれていますね。
ここでもシグネチャが大事になってきます。
警告されるようになった実装
リリースノートのサンプルに戻ります。
以下がVetによって警告されるようになったとのことですが、具体的に言うとIs
のシグネチャが間違っていますね。errors.Is
を有効に使用するにはIs(err error) bool
として実装する必要があります。
↓の実装だとどんなに頑張ってもerrors.Is
がfalse
を返すので、何もないと貴重な時間を無駄にしたり、最悪の場合バグに気づかずリリースなんてことにもなりかねません。今回の修正でVetが怒ってくれるようになったので、そんな不幸なことが起きることが無くなったわけですね。
type MyError struct { hint string } |
最後に
というわけでerror#Is/As/Unwrap
ネタでした。
少しだけ平和な世界に近づいたようです。
次回は連載最後で市川さんの記事です。