Go言語におけるIsとAsの使い方とその違いについて
どうもお疲れ様です。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