Go のエラーのラップとアンラップ(errors.Is / errors.As)

Go 1.13 でエラーのラップ機能が導入され、errors.Iserrors.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.Is

エラーチェーンをたどって比較するため、ラップされていても検出できる。

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.Iserrors.As を適切に使い分けることで、堅牢なエラーハンドリングが実現できる。