Help us understand the problem. What is going on with this article?

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

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 独自の処理を扱えるようになるので良いですね.

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした