Golang 1.13 errors ライブラリによるerror値の取り扱い
Golang
Lastmod: 2020-10-19

error のラッピング

Go 1.13 より、 fmt.Errorf が error インタフェースを実装する値のラッピングをサポートした。この機能により、ある error 値を別の error に内包する事ができる。

func UpdateHoge(hoge: Hoge) error {
    err := app.UpdateHoge(hoge)
    if err != nil {
        return fmt.Errorf("Internal Server Error : %w", err)
    }
}

%wverb を format 内に含めることで、error 文字列内に第 2 引数以降にとる error 文字列を含めつつ、内部に error を保持させることができる。新規に作成した error がラップする error を取り出すには Unwrap() 関数を使用する。この関数は同じく Go 1.13 からサポートされるライブラリ errors 内に含まれる。

func Update(){
    hoge := NewHoge()
    err := UpdateHoge(hoge)
    if err != nil {
        panic(errors.Unwrap(err))
    }
}

この errors.Unwrap(error) 関数は、引数に取る error が Unwrap() を実装していればその関数が返す error 値を、そうでなければ nil を返す。そのため、自前の error ラッピング構造体を定義したい場合、 Unwrap() 関数を定義する。

type AppError struct {
    err  error
    msg  string
    code ErrorCode
}

func (e AppError) Error() string {
    return e.msg
}

func (e AppError) Unwrap() error {
    return e.err
}

fmt.Errorf() のラッピングよって生成される error もこの Unwrap() を定義している様子。

cerr := errors.New("child error")
perr := fmt.Errorf("parent error : %w", cerr)

fmt.Println(perr) // parent error : child error
fmt.Println(errors.Unwrap(perr)) // child error
fmt.Println(errors.Unwrap(cerr)) // <nil>

error の実装する Unwrap() から取得できる error もまた、 Unwrap() を実装しうる。この Unwrap() の連鎖から取得できる一連の error を公式では err’s chain と呼んでいる。この chain によって、複数の error 表現をコード内で取り回すことができる。

err's chain 内 error の有無チェック

errors パッケージでは、err’s chain のためのの比較用関数も提供する。

errors.Is() は、第 1 引数の err’s chain に第 2 引数の error が含まれるかどうかを bool で返す。

cerr := errors.New("child error")
perr := fmt.Errorf("parent error : %w", cerr)

fmt.Println(errors.Is(cerr, cerr)) // true
fmt.Println(errors.Is(cerr, perr)) // false
fmt.Println(errors.Is(perr, cerr)) // true

err's chain に含まれる error の参照

errors.As() 関数は、第 1 引数にとる err’s chain のうち、第 2 引数と一致する error 型の値があれば、その値で第 2 引数の参照先を置き換えて true を返す。すなわち、第 2 引数は error ポインタ型となる。第 2 引数に他の型の値、及びポインタが渡されればパニックになり、nil が渡されれはfalse を返す。

cerr := errors.New("child error")
perr := fmt.Errorf("parent error : %w", cerr)

fmt.Println(errors.As(perr, &cerr)) // true
fmt.Println(errors.As(perr, nil)) // false

エラーチェーン中に特定の具象 error が含まれれば特定の処理を実行しつつ具象 error を参照する、といった場合に有用そう。

// id : Int
prod, err := GetProduct(id)
var aerr AppError
if errors.As(err, &aerr) {
    fmt.Errorf("error - %s", aerr)
}

参考文献

errors - The Go Programming Language