0Pricing
Go Academy · レッスン

エラーのラップとアンラップ

%w、errors.Is、errors.Asの利用

「エラーのラップとアンラップ」はCoddyKit上の無料Go Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはGo Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Go Academyコースには全4レッスンが含まれています。

エラーラッピングとは

エラーラッピングは、新しいエラーの内部に元のエラーを埋め込みます。これにより、コンテキストを追加しながら、プログラムから元のエラーを調べられます。Go 1.13 で正式に導入されました:

package main
import ("fmt"; "errors")

var ErrDB = errors.New("database unreachable")

func queryUsers() error {
    return fmt.Errorf("queryUsers: %w", ErrDB)
}

func loadProfile(id int) error {
    return fmt.Errorf("loadProfile(%d): %w", id, queryUsers())
}

func main() {
    err := loadProfile(42)
    fmt.Println(err) // loadProfile(42): queryUsers: database unreachable
}

%w 動詞

エラーをラップするには、fmt.Errorf で %v ではなく %w を使用します。ラップされたエラーには errors.Unwrap からアクセスできます:

package main
import ("fmt"; "errors")

func main() {
    inner := errors.New("inner error")
    outer := fmt.Errorf("outer: %w", inner)

    fmt.Println(outer)              // outer: inner error
    fmt.Println(errors.Unwrap(outer)) // inner error
    fmt.Println(errors.Is(outer, inner)) // true
}

errors.Unwrap

errors.Unwrap はチェーン内の次のエラー(ラップされたエラー)を返します。次のエラーがない場合は nil を返します:

package main
import ("fmt"; "errors")

type Layer struct{ msg string; err error }
func (l *Layer) Error() string { return l.msg }
func (l *Layer) Unwrap() error { return l.err }

func main() {
    root := errors.New("root")
    mid  := &Layer{"mid", root}
    top  := &Layer{"top", mid}

    fmt.Println(errors.Unwrap(top))  // mid
    fmt.Println(errors.Unwrap(mid))  // root
    fmt.Println(errors.Unwrap(root)) // <nil>
}

errors.Is — チェーンをたどる

errors.Is(err, target) はエラーチェーンを再帰的にアンラップし、一致するエラーを見つけるか nil に到達するまで調べます:

package main
import ("fmt"; "errors")

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

func findItem(id int) error {
    return fmt.Errorf("findItem(%d): %w",
        id, fmt.Errorf("repo: %w", ErrNotFound))
}

func main() {
    err := findItem(7)
    fmt.Println(errors.Is(err, ErrNotFound)) // true — deep in chain
    fmt.Println(err)                         // full message chain
}

errors.As — 型のためのチェーン走査

errors.As(err, &target) はチェーンをたどり、target の型に代入可能なエラーを探します:

package main
import ("fmt"; "errors")

type StatusError struct{ Code int }
func (e *StatusError) Error() string { return fmt.Sprintf("status %d", e.Code) }

func doRequest() error {
    return fmt.Errorf("request: %w", &StatusError{503})
}

func main() {
    err := doRequest()
    var se *StatusError
    if errors.As(err, &se) {
        fmt.Println("status code:", se.Code) // 503
    }
}

チェーンのためのカスタム Unwrap

カスタムエラー型に Unwrap() error を実装すると、errors.Is と errors.As に対応できます:

package main
import ("fmt"; "errors")

type AppError struct {
    Op  string
    Err error
}
func (e *AppError) Error() string  { return fmt.Sprintf("%s: %v", e.Op, e.Err) }
func (e *AppError) Unwrap() error  { return e.Err }

var ErrAuth = errors.New("unauthorized")

func main() {
    err := &AppError{"login", ErrAuth}
    fmt.Println(errors.Is(err, ErrAuth)) // true
}

fmt.Errorf における %v と %w

%v と %w の違い:

package main
import ("fmt"; "errors")

func main() {
    inner := errors.New("inner")

    withV := fmt.Errorf("outer: %v", inner) // string only, no wrap
    withW := fmt.Errorf("outer: %w", inner) // wraps inner

    fmt.Println(errors.Is(withV, inner)) // false — not wrapped
    fmt.Println(errors.Is(withW, inner)) // true  — wrapped
}

実践でのラッピング:HTTP ハンドラー

一貫したエラーラッピングにより、本番システムでのデバッグが容易になります:

package main
import "fmt"

func dbQuery(sql string) error {
    return fmt.Errorf("db.Query(%q): connection refused", sql)
}

func getUserByID(id int) (string, error) {
    if err := dbQuery("SELECT * FROM users WHERE id=?"); err != nil {
        return "", fmt.Errorf("getUserByID(%d): %w", id, err)
    }
    return "Alice", nil
}

func handler() error {
    _, err := getUserByID(1)
    if err != nil {
        return fmt.Errorf("handler: %w", err)
    }
    return nil
}

func main() { fmt.Println(handler()) }

errors.Join — 複数のラップ(Go 1.20 以降)

errors.Join は複数のエラーをラップするエラーを作成します。errors.Is はそれらすべてをチェックします:

package main
import ("fmt"; "errors")

var ErrA = errors.New("err A")
var ErrB = errors.New("err B")

func main() {
    joined := errors.Join(ErrA, ErrB)
    fmt.Println(joined)
    fmt.Println(errors.Is(joined, ErrA)) // true
    fmt.Println(errors.Is(joined, ErrB)) // true
}

アンラップのベストプラクティス

エラーラッピングの指針:

  • プログラムから調べられるように、%w でラップしてエラーを保持します
  • コンテキストとして操作名を追加します:fmt.Errorf("funcName: %w", err)
  • 呼び出し側が errors.Is を使用しない場合は、センチネルエラーをラップしません
  • すべてのレベルで同じコンテキストを二重にラップしないようにします
  • カスタム型に Unwrap() を実装してチェーンに参加させます

確認問題

fmt.Errorf で %v と %w を使用する場合の違いは何ですか?

まとめ:エラーのラッピングとアンラップ

まとめ:

  • fmt.Errorf("ctx: %w", err) はコンテキスト付きでラップします
  • errors.Unwrap はチェーン内の次のエラーを取得します
  • errors.Is は同一性の一致を求めてチェーンをたどります
  • errors.As は型の一致を求めてチェーンをたどります
  • カスタム型は Unwrap() error を実装します
  • errors.Join は複数のエラーをラップします(Go 1.20 以降)

よくある質問

「エラーのラップとアンラップ」レッスンは無料ですか?

はい。「エラーのラップとアンラップ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Go Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Go Academyコースには全4レッスンが含まれています。

「エラーのラップとアンラップ」で何を学びますか?

%w、errors.Is、errors.Asの利用 ブラウザで直接実行するハンズオンコードでGo Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Go Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのGo Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「エラーのラップとアンラップ」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このGo Academyレッスンでコードを書いて実行できますか?

はい。すべてのGo Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. errorインターフェース
  2. カスタムエラーの作成
  3. エラーのラップとアンラップ
  4. panic、recover、defer
← Go Academyに戻る