Go のエラーのラップとアンラップ(errors.Is / errors.As)
Go 1.13 でエラーのラップ機能が導入され、errors.Is と errors.As という2つの重要な関数が追加された。これらを使うと、ラップされたエラーの中から特定のエラーを検出できる。
エラーのラップとは
エラーをラップするとは、元のエラーに追加情報を付けて新しいエラーを作ることだ。fmt.Errorf の %w 動詞を使う。
originalErr := errors.New("file not found")
wrappedErr := fmt.Errorf("failed to load config: %w", originalErr)wrappedErr は originalErr を内部に保持している。エラーメッセージには両方の情報が含まれるが、元のエラーとの関係性も維持される。
errors.Unwrap
errors.Unwrap は、ラップされたエラーから元のエラーを1つ取り出す。
inner := errors.Unwrap(wrappedErr)
fmt.Println(inner) // file not foundラップされていないエラーに対して呼ぶと nil を返す。多重にラップされている場合は、1段階だけアンラップする。
errors.Is による比較
errors.Is は、エラーチェーンの中に特定のエラーが含まれているかを判定する。
var ErrNotFound = errors.New("not found")
func process() error {
err := fetchData()
if err != nil {
return fmt.Errorf("process failed: %w", err)
}
return nil
}
func main() {
err := process()
if errors.Is(err, ErrNotFound) {
fmt.Println("データが見つかりません")
}
}errors.Is はエラーチェーンを再帰的にたどり、どこかに ErrNotFound があれば true を返す。単純な == 比較ではラップされたエラーを検出できないため、errors.Is を使う必要がある。
ラップされたエラーと元のエラーは別のオブジェクトなので false になる。
エラーチェーンをたどって比較するため、ラップされていても検出できる。
errors.As による型アサーション
errors.As は、エラーチェーンの中から特定の型のエラーを取り出す。
type PathError struct {
Path string
Err error
}
func (e *PathError) Error() string {
return fmt.Sprintf("path error: %s: %v", e.Path, e.Err)
}
func main() {
err := doSomething()
var pathErr *PathError
if errors.As(err, &pathErr) {
fmt.Println("問題のパス:", pathErr.Path)
}
}errors.As は第2引数にポインタを渡し、マッチした場合にそこへ値を代入する。型アサーションと似ているが、エラーチェーン全体を検索する点が異なる。
実践的なパターン
以下は HTTP クライアントでのエラーハンドリング例だ。
func fetchUser(id int) (*User, error) {
resp, err := http.Get(fmt.Sprintf("/users/%d", id))
if err != nil {
var netErr *net.OpError
if errors.As(err, &netErr) {
return nil, fmt.Errorf("network error: %w", err)
}
return nil, fmt.Errorf("request failed: %w", err)
}
// ...
}errors.As でネットワークエラーかどうかを判定し、エラーの種類に応じた処理を行っている。errors.Is と errors.As を適切に使い分けることで、堅牢なエラーハンドリングが実現できる。