Goの新しいerrors パッケージ xerrors

先日 [xerrors](https://godoc.org/golang.org/x/xerrors) パッケージがリリースされました。
このパッケージは、[Proposal: Go 2 Error Inspection](https://go.googlesource.com/proposal/+/master/design/29934-error-values.md) で提案されているものをGo1向けに外部ライブラリとして試験的に実装したものです。
Goの標準ライブラリではありませんが、Go公式がメンテナンスをしています。

このパッケージができた背景は、今まで多くのGoエンジニアは下位層のエラーの情報を伝播させるために  [pkg/errors](https://godoc.org/github.com/pkg/errors) パッケージ などの外部ライブラリを利用していました。この手法が開発者の間で普及したため標準ライブラリで正式に検討を始めることとなりました。


#### 2019/9/4更新
Go 1.13では `%w` でのラップや `Is` メソッド、 `As` メソッドは正式に導入されました。
しかし`%+w` や `%+v` によるスタックトレースの表示の採用は見送られました。

スタックトレースの表示が必要な場合はxerrors パッケージを利用して、不要な場合には標準ライブラリの errors パッケージを利用してください。

この記事ではxerrorsパッケージの仕様を紹介します。

#### 2022/6/26更新
xerrors packageに[更新](https://github.com/golang/xerrors/compare/cd4e9df8ec86741d9a6ceaa2da097cea8e06cc3e~...65e65417b02f28de84b55f16b46a1e789149973a)があり、Is、As、UnwrapはDeprecatedになりました。今後はerrors packageの同名の関数を利用する必要があります。また `fmt.Errorf` がスタックトレースを表示できず `xerrors.Errorf` の代わりにはならないため `xerrors.Errorf` はDeprecatedになっていません。


## 基本

以下のパッケージをimportします。

```go
import "golang.org/x/xerrors"
```

## フォーマットとラップ


### 文字列からのエラーを作成する

```go
err := xerrors.New("error in main method")
fmt.Printf("%v\n", err)
```
```
error in main method
```

xerrors.Newで作成したエラーは、`%+v` のときにファイル名やメソッド名を表示します。

```go
err := xerrors.New("error in main method")
fmt.Printf("%+v\n", err)
```
```
error in main method:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:9
```

`%#v` では以下のようになります。

```go
err := xerrors.New("error in main method")
fmt.Printf("%#v\n", err)
```
```
error in main method
```


### 既存のエラーから新規のエラーを作成する

```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method %v",baseErr)
fmt.Printf("%+v\n", err)
```
```
error in main method base error:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:10
```

このやり方だと、baseErrの行数の情報が失われてしまっています。
以下のように `%v` の前にコロンとスペースを加えて `: %v` とすることで既存のerrorの情報を出力することができます。

```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %v", baseErr)
fmt.Printf("%+v\n", err)
```
```
error in main method:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:11
  - base error:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:10
```

`%v` だけではなく `%s` でも問題ありません。


### エラーをラップする

```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %w", baseErr)
fmt.Printf("%+v\n", err)
```
```
error in main method:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:11
  - base error:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:10
```

`: %w` でラップできますが、 `%w` では正しくラップできません。

```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method %w", baseErr)
fmt.Printf("%+v\n", err)
```
```
error in main method %!w(*xerrors.errorString):
    main.main
        /Users/sonatard/tmp/xerrors/main.go:11
```
`: %w` の理由を知りたい方はこちらをご覧ください。
[xerrorsパッケージがWrapメソッドではなく : %w でラップする理由](https://qiita.com/sonatard/items/7b15258fa19f939b1323)

### エラーをアンラップする

```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %w", baseErr)
fmt.Printf("%+v\n", xerrors.Unwrap(err))
```
```
base error:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:10
```

アンラップできるエラーは `: %w` でラップしたものだけであり、`: %v` や `: %s` ではラップされていないためアンラップできません。

```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %v", baseErr)
fmt.Printf("%+v\n", xerrors.Unwrap(err))
```
```
<nil>
```

## エラーの同一性をチェックする

### 通常
```go
baseErr := xerrors.New("base error")
fmt.Printf("%v\n", xerrors.Is(baseErr, baseErr))
fmt.Printf("%v\n", baseErr == baseErr)
```
```
true
true
```

### ラップした場合
```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %w", baseErr)
fmt.Printf("%v\n", xerrors.Is(err, baseErr))
fmt.Printf("%v\n", err == baseErr)
```
```
true
false
```

`Is` メソッドを使うことで、errの中のラップされたbaseErrが同一と判断されます。

### 複数回ラップした場合
```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %w", baseErr)
err2 := xerrors.Errorf("error2 in main method: %w", err)
fmt.Printf("%v\n", xerrors.Is(err, baseErr))
fmt.Printf("%v\n", xerrors.Is(err2, baseErr))
fmt.Printf("%v\n", xerrors.Is(err2, err))
```
```
true
true
true
```

すべて `true` となります。

### Opaqueメソッド実行後の同一性

```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %w", baseErr)
err2 := xerrors.Opaque(err)
fmt.Printf("%v\n", xerrors.Is(err, baseErr))
fmt.Printf("%v\n", xerrors.Is(err2, baseErr))
fmt.Printf("%v\n", err)
fmt.Printf("%v\n", err2)
fmt.Printf("%v\n", xerrors.Unwrap(err2))
```
```
true
false
error in main method: base error
error in main method: base error
<nil>
```

`Opaque` メソッドを実行すると、同じエラーフォマットの別のエラーが返ってきます。このエラーはIsメソッドで比較すると` false` になりますが出力結果は同じになります。また `Unwrap` を実行することはできません。

### 途中のエラーにOpaqueメソッドを適用したエラーの同一性
```go
baseErr := xerrors.New("base error")
err := xerrors.Errorf("error in main method: %w", baseErr)
err2 := xerrors.Errorf("error2 in main method: %w", xerrors.Opaque(err))
err3 := xerrors.Errorf("error3 in main method: %w", err2)
fmt.Printf("%v\n", xerrors.Is(err2, baseErr))
fmt.Printf("%v\n", xerrors.Is(err2, err))
fmt.Printf("%v\n", xerrors.Is(err3, err2))
```
```
false
false
true
```

errにOpaqueを適用してerr2にラップしているため、err2とerrを含んだラップは同一ではないと判断されます。
その後追加でラップしているerr3とerr2は同一と判断されます。



## エラーの型変換

以降の説明で登場する独自に定義した型です。

```go
type BaseError struct {
	msg string
}

func (e *BaseError) Error() string {
	return e.msg
}
```

### 通常

```go
	var baseErr error = &BaseError{msg: "base error"}
	var baseErr2 *BaseError
	if ok := xerrors.As(baseErr, &baseErr2); !ok {
		fmt.Printf("As failed\n")
	}
	fmt.Printf("%v\n", baseErr2 == baseErr)
```
```
true
```

`As` メソッドの第2引数に渡した型に変換しています。

### ラップした型
```go
	var baseErr error = &BaseError{msg: "base error"}
	err := xerrors.Errorf("error in main method: %w", baseErr)
	var baseErr2 *BaseError
	if ok := xerrors.As(err, &baseErr2); !ok {
		fmt.Printf("As failed\n")
	}
	fmt.Printf("%v\n", baseErr2 == baseErr)
```
```
true
```

先ほどと同様ですが、ラップした型は一致しないため更に下位層のbaseErrに変換されます。

### Asのターゲットの型がerror chainに存在しない場合

```go
	var baseErr error = &BaseError{msg: "base error"}
	err := xerrors.Errorf("error in main method: %w", baseErr)
	var baseErr2 *ABaseError
	if ok := xerrors.As(err, &baseErr2); !ok {
		fmt.Printf("As failed\n")
	}
	fmt.Printf("%v\n", baseErr2 == baseErr)
```
```
As failed
false
```

失敗した場合は `ok` が `false` になります。

## サンプル

最後にもう少し実践的なサンプルコードを紹介します。

```go
package main

import (
	"fmt"

	"golang.org/x/xerrors"
)

var ErrNotFound = &SampleError{
	statusCode: 404,
	level:      "Error",
	msg:        "not found",
}

type SampleError struct {
	level      string
	statusCode int
	msg        string
}

func (e *SampleError) Error() string {
	return fmt.Sprintf("%s: code=%d, msg=%s", e.level, e.statusCode, e.msg)
}

func main() {
	err := func1()
	if err != nil {
		var sampleErr *SampleError
		if xerrors.As(err, &sampleErr) {
			switch sampleErr.level {
			case "Fatal":
				fmt.Printf("Fatal！ %v\n", sampleErr)
			case "Error":
				fmt.Printf("Error！ %v\n", sampleErr)
			case "Warning":
				fmt.Printf("Warning！ %v\n", sampleErr)
			}
		}

		fmt.Printf("%+v\n", err)
		return
	}

	fmt.Printf("エラーなし\n")
}

func func1() error {
	err := func2()
	if err != nil {
		return xerrors.Errorf("func1 error: %w", err)
	}
	return nil
}

func func2() error {
	err := func3()
	if err != nil {
		return xerrors.Errorf("func2 error: %w", err)
	}
	return nil
}
func func3() error {
	return ErrNotFound
}
```
```
Error！ Error: code=404, msg=not found
func1 error:
    main.func1
        /Users/sonatard/tmp/xerrors/main.go:45
  - func2 error:
    main.func2
        /Users/sonatard/tmp/xerrors/main.go:53
  - Error: code=404, msg=not found
```

余裕があればこちらも理解することで、より適切な設計ができます。
[xerrors - エラー設計の注意点](https://qiita.com/sonatard/items/c8d0ae72c50f625522a0)
[xerrors パッケージ - 独自に定義したエラー型はIsメソッドとAsメソッドでデフォルトの振る舞いを変更可能](https://qiita.com/sonatard/items/c6da1a895b2ca7e0f13f) 
[エラーを検査する](https://qiita.com/methane/items/748014cbb5f71ee6f04f) 


## 関連情報
- [xerrors - 関連情報](https://qiita.com/sonatard/items/802db82e7275f17fe702)