🟰

Go言語におけるIsとAsの使い方とその違いについて

2024/02/12に公開

どうもお疲れ様です。MESIです。

Goのエラーハンドリングについて勉強していて、IsとAsの使い方がいまいち理解できなかったので、まとめることにしました。
本記事では、IsとAsの違い、それぞれがなぜ必要なのか、そして具体的な使用例について解説します。

なぜerrors.Isが必要なのか?

「エラーのチェックは==演算子で比較すればよくない?」
そう思っていましたが、そうは行かないケースもあることを学びました。

Go言語では、エラーはerrorインターフェースを実装する任意の型で表現されます。

type error interface {
    Error() string
}

従来、エラーの同一性をチェックする際には==演算子を使用することが一般的でしたが、これには限界があります。
==演算子は、エラーの値そのものが完全に一致する場合にのみtrueを返します。
しかし、エラーが他のエラーをラップしている場合(例えば、下の例のようにfmt.Errorfの%wを使用してエラーをラップした場合)、==演算子では期待する結果を得ることができなくなります。

package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("not found")

func getFile() error {
	return fmt.Errorf("file processing error: %w", ErrNotFound)
}

func main() {
	err := getFile()
	if errors.Is(err, ErrNotFound) { // ラップされているので==で比較できない
		fmt.Println("Error: File not found")
	} else {
		fmt.Println("Error: Other error")
	}
}

==で比較できないときはerrors.Is関数を使用して、エラーチェーン内にErrNotFoundエラーが存在するかをチェックします。
これにより、エラーがラップされていたとしても、期待するエラーを正確に識別できます。

なぜerrors.Asが必要なのか?

これも使い道がよくわかってませんでした。
重要なのは、Asでは値ではなく型をチェックするということです。
エラーが特定の型であるかをチェックし、その型に基づいて特定の処理を行いたい場合にerrors.Asが必要になります。

errors.Asはエラーチェーンをたどり、指定された型のエラーが見つかると、そのエラーへの参照を提供します。
errors.Asを使うときは、2つの引数を受け取ります。ひとつ目が調査中のエラーで、2つ目が探している型の変数を指すポインタです。

package main

import (
	"errors"
	"fmt"
)

type MyError struct {
	Code int
	Msg  string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("Code: %d, Message: %s", e.Code, e.Msg)
}

func operation() error {
	return &MyError{Code: 404, Msg: "Resource not found"}
}

func main() {
	err := operation()
	var myErr *MyError
	if errors.As(err, &myErr) {
		fmt.Printf("Custom error: %v\n", myErr) // 一致したエラー型へのアクセスが可能
	} else {
		fmt.Println("Generic error")
	}
}

この例では、errors.Asを使用してエラーがMyError型であるかをチェックし、そのエラー型にアクセスしています。これにより、型に基づいたエラー情報の取得や特定のエラー処理が可能になります。

まとめ:IsとAsの違い

  • errors.Is
    • エラーが特定のと一致するかどうかをチェックする。
      • エラーの同一性を確認するのに用いられる。
  • errors.As
    • エラーが特定のであるかをチェックし、そのエラー型にアクセスするために使用される。
      • 型の一致を確認し、そのエラー型へのアクセスを可能にする

Discussion