TL;DR
これまで Go 1.13 から登場した errors.As の使い所があまり良くわからなかったのですが,
全ての答えは公式 Doc にありました.
In the simplest case, the errors.Is function behaves like a comparison to a sentinel error, and the errors.As function behaves like a type assertion.
-
errors.Is: 特定のエラーとの比較 -
errors.As: エラーに対する型アサーション
ここでは実例をもとに理解を深めていきたいと思います.
実例 error.Is
errors.Isはあるエラーが特定のエラーを Wrap したものかを判別するためのものです.
import (
"fmt"
"errors"
)
ErrFoo := errors.New("foo error")
func main() {
wrapped := fmt.Errorf("wrapped woo: %w", ErrFoo)
if errors.Is(wrapped, ErrFoo) {
fmt.Println("this error is caused by %v", ErrFoo)
}
}
こんな感じで特定のエラーの起源をたどって,例外処理ができます.
比較的単純で「なるほど,便利.」という感じです.
実例 errors.As
errros.As は冒頭でも書いたとおり,エラーに対する型アサーションです.
言い換えると自前でエラー interface を書いたときに,ビルトインのエラーなのか自前で用意したエラーなのかを判別するときに使えます.
また,その付加効果として(こっちがメインかもしれないが),動的に自前のエラー独自のメソッドを利用することができます.
少し長いですがここでは go-playground/validator を用いた例について紹介します.
コード自体は基本的に main 部に注目してくれればよいです.
import (
"fmt"
"errors"
"github.com/go-playground/validator/v10"
)
type Foo struct {
NaturalNumber int `validate:"gt=0"`
}
func validateFoo(f *Foo) error {
v := validator.New()
return v.Struct(f)
}
func buildFoo(n int) (*Foo, error) {
foo := &Foo{NaturalNumber: n}
if err := validateFoo(foo); err != nil {
return nil, fmt.Errorf("cannot build Foo: %w", err)
}
return foo, nil
}
func main() {
foo, err := buildFoo(-1)
if err != nil {
var verrs validator.ValidationErrors
if errors.As(err, &verrs) {
for _, verr := range verrs {
fmt.Println(verr)
}
return
}
fmt.Printf("unknown error is occurred: %v", err)
return
}
fmt.Printf("this is %v", foo)
}
go-playground/validator はいわゆる validation エラーを扱うためのパッケージで, ValidationErrors という独自のエラーを内部で持っています.
ここで buildFoo は Foo インスタンスを作成するための関数で,validation エラーが起きた場合はそれを Wrap して返すものとします.
よって,返ってくるエラーは特定のエラーインスタンスを Wrap したものではなく, ValidationErrors という interface を満たしたものを Wrap していることに注意してください.つまり,エラーインスタンスに対する比較である errors.Isはここで用いることはできません.
そこで型アサーションの役割を果たす errors.As を使います.
errors.As では err を UnWrap していくと verr (ValidationErrors) として扱うことができるかということをチェックします.
もし可能なのであれば, errors.As は true を返しつつ,verr に Wrap して出てきた ValidationErrors を代入します.このため if 文の中では verrs は err から抽出された実態を持たせて扱うことができ,動的に ValidationErrors の機能を用いることができます.
実際に上記コードの if 文内の for 文は ValidationErrors が持っている機能です.
まとめ
実例を見てみると,
-
errors.Is: 特定のエラーとの比較 -
errors.As: エラーに対する型アサーション
というのはかなりしっくりきます.
加えて errors.As は動的に interface 独自の処理を扱えるようになるので良いですね.
参考
- 公式 Doc
- Go 1.13時代のエラー実装者のお作法: 実装面の解説でとてもわかりやすかったです!