fmt.Errorf()とerrorsのセットとgolang.org/x/xerrorsは異なる
前回の記事でfmt.Errorf()がpkg/errors.Wrap()を殺すという話を書いた。これはもう少し雑に言えば、エラー時に位置情報を記録したいということだった。
そしてこれについて以下の様な発言をしつつ調べていたところ、かつてのpolyfilだと思っていたgolang.org/x/xerrorsが、go1.13以降であっても標準ライブラリのerrorsとfmtパッケージのセットとの間に違いがあることに気づいた。
go、手塩にかけたこだわりの作品的なツールを書くことに念頭を起きすぎている様な気がするなー。
— po (@podhmo) 2020年9月17日
(他人が書いたよくわかんないコードを触るときのことはあんまり考えていないような気がする)
ちなみにpkg/errorsのWrap()で保持されるtracebackの情報が%wで包まれると"%+v"を使っても取り出せなくなる。
— po (@podhmo) 2020年9月17日
(fmt.wrapErrorのFormat()実装がそうなので)
あー、もしかしてerrorsとfmt.Errorf()のセットと、https://t.co/ND934ToNSg って別物?
— po (@podhmo) 2020年9月17日
個人的に気にしていた違いは以下。
- 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
- https://gist.github.com/podhmo/78299bd52622f6a725b772d601dc34e3
- https://gist.github.com/podhmo/180b1aee18c574e495030514e5f27c85
- https://gist.github.com/podhmo/7912d6b7c4c8d1bc5a9c521aa3b8a966
参考
misc
zennを始めてみた。続くかもしれないし、続かないかもしれない。