fmt.Errorf()とerrorsのセットとgolang.org/x/xerrorsは異なる

前回の記事でfmt.Errorf()がpkg/errors.Wrap()を殺すという話を書いた。これはもう少し雑に言えば、エラー時に位置情報を記録したいということだった。

そしてこれについて以下の様な発言をしつつ調べていたところ、かつてのpolyfilだと思っていたgolang.org/x/xerrorsが、go1.13以降であっても標準ライブラリのerrorsとfmtパッケージのセットとの間に違いがあることに気づいた。

個人的に気にしていた違いは以下。

  • fmt.Errorf()とerrorsのセット は位置情報を記録しない
  • golang.org/x/xerros は位置情報を記録する

両者は同じものだと思っていたが違っていた。

fmt.Errorf()とerrorsのセット

位置情報を記録しない。

package main

import (
    "fmt"
    "io"

    "github.com/k0kubun/pp"
)

func main() {
    err := fmt.Errorf("xxx: %w", fmt.Errorf("yyy: %w", io.EOF))
    fmt.Printf("!! %+v", err)
    pp.Println(err)

    // &fmt.wrapError{
    //   msg: "xxx yyy EOF",
    //   err: &fmt.wrapError{
    //     msg: "yyy EOF",
    //     err: &errors.errorString{
    //       s: "EOF",
    //     },
    //   },
    // }
}

golang.org/x/xerrors

位置情報を記録する。

package main

import (
    "fmt"
    "io"

    "github.com/k0kubun/pp"
    "golang.org/x/xerrors"
)

func main() {
    err := xerrors.Errorf("xxx: %w", xerrors.Errorf("yyy: %w", io.EOF))
    fmt.Printf("!! %+v\n", err)
    pp.Println(err)
    // &xerrors.wrapError{
    //   msg: "xxx",
    //   err: &xerrors.wrapError{
    //     msg: "yyy",
    //     err: &errors.errorString{
    //       s: "EOF",
    //     },
    //     frame: xerrors.Frame{
    //       frames: [3]uintptr{
    //         0x10cc030,
    //         0x10cde4d,
    //         0x1031e0a,
    //       },
    //     },
    //   },
    //   frame: xerrors.Frame{
    //     frames: [3]uintptr{
    //       0x10cc030,
    //       0x10cdea7,
    //       0x1031e0a,
    //     },
    //   },
    // }
}

%+vの表示

実際のところ前回の記事と同様のコードを書いてみるといい感じの出力を返してくれる。main()の中でf(),g(),h()と順に読んでいくようなコード。

スタックトレースがほしい場合には、xerrorsを使うようにしてみるのもありかもしれない。

2020/09/19 08:38:24 ! on f: on g: hmm
2020/09/19 08:38:24 !!on f:
    main.f
        ./03errors/main03.go:15
  - on g:
    main.g
        ./03errors/main03.go:18
  - hmm

このときのコードは以下。

package main

import (
    "fmt"
    "log"

    "golang.org/x/xerrors"
)

func main() {
    log.Printf("! %v", f())
    log.Printf("!!%+v", f())
}
func f() error {
    return xerrors.Errorf("on f: %w", g())
}
func g() error {
    return xerrors.Errorf("on g: %w", h())

}
func h() error {
    return fmt.Errorf("hmm")
}

という情報が試してみるまで見つけられなかったのがとても不思議。

もう少し詳しく

実際のところドキュメントを真面目に読むとGo 2のproposalの実装と書かれていた。

Package xerrors implements functions to manipulate errors. This package is based on the Go 2 proposal for error values:

https://golang.org/design/29934-error-values

These functions were incorporated into the standard library's errors package in Go 1.13: - Is - As - Unwrap Also, Errorf's %w verb was incorporated into fmt.Errorf. Use this package to get equivalent behavior in all supported Go versions. No other features of this package were included in Go 1.13, and at present there are no plans to include any of them.

実は位置情報の記録はこの内の「No other features」に含まれていたということのよう。

gist

参考

misc

zennを始めてみた。続くかもしれないし、続かないかもしれない。

zenn.dev