LoginSignup
14
8

More than 3 years have passed since last update.

[Go 1.13~] errors.Is と errors.As の違いについてお気持ちを理解する

Last updated at Posted at 2020-02-17

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 という独自のエラーを内部で持っています.

ここで buildFooFoo インスタンスを作成するための関数で,validation エラーが起きた場合はそれを Wrap して返すものとします.
よって,返ってくるエラーは特定のエラーインスタンスを Wrap したものではなく, ValidationErrors という interface を満たしたものを Wrap していることに注意してください.つまり,エラーインスタンスに対する比較である errors.Isはここで用いることはできません.

そこで型アサーションの役割を果たす errors.As を使います.
errors.As では err を UnWrap していくと verr (ValidationErrors) として扱うことができるかということをチェックします.
もし可能なのであれば, errors.Astrue を返しつつ,verr に Wrap して出てきた ValidationErrors を代入します.このため if 文の中では verrserr から抽出された実態を持たせて扱うことができ,動的に ValidationErrors の機能を用いることができます.
実際に上記コードの if 文内の for 文は ValidationErrors が持っている機能です.

まとめ

実例を見てみると,

  • errors.Is: 特定のエラーとの比較
  • errors.As: エラーに対する型アサーション

というのはかなりしっくりきます.
加えて errors.As は動的に interface 独自の処理を扱えるようになるので良いですね.

参考

14
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
8