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時代のエラー実装者のお作法: 実装面の解説でとてもわかりやすかったです!