Go アドベントカレンダーその 2 の 10 日目のエントリーです。
はじめに
Formatter でよく見かける %v
と %+v
といった +
の違いを確認してみます。はじめに公式のドキュメント https://golang.org/pkg/fmt/ を確認しましょう。以下のようにあります。
when printing structs, the plus flag (%+v) adds field names
公式ドキュメントのとおり、構造体を print するときに %+v
と +
を付与するとフィールド名が付与されて出力されます。
package main
import "fmt"
func main() {
p := &Person{
id: 1,
Name: "tutuz",
Country: "Japan",
}
fmt.Printf("%v\n", p)
fmt.Println("-----------------------------")
fmt.Printf("%+v\n", p)
}
type Person struct {
id int
Name string
Country string
}
- 出力結果
&{1 tutuz Japan}
-----------------------------
&{id:1 Name:tutuz Country:Japan}
独自 Format 書式
ところで Go でエラーを扱うときに pkg/errors パッケージを使っていることが多いと思います。pkg/errors で %+v
とするとスタックトレースを出力できます。こんな感じです。
package main
import (
"fmt"
"strconv"
"github.com/pkg/errors"
)
func main() {
err := errorN()
fmt.Printf("error %T\n", err)
fmt.Printf("error %+v\n", err)
fmt.Println("--------------------------------------------------")
err = errorS()
fmt.Printf("error %T\n", err)
fmt.Printf("error %+v\n", err)
}
func errorN() error {
return errors.New("My error occurred.")
}
func errorS() error {
_, err := strconv.ParseBool("XXX")
return errors.WithStack(err)
}
error *errors.fundamental
error My error occurred.
main.errorN
C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/fmt/error/main.go:22
main.main
C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/fmt/error/main.go:11
runtime.main
C:/Go/src/runtime/proc.go:200
runtime.goexit
C:/Go/src/runtime/asm_amd64.s:1337
--------------------------------------------------
error *errors.withStack
error strconv.ParseBool: parsing "XXX": invalid syntax
main.errorS
C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/fmt/error/main.go:27
main.main
C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/fmt/error/main.go:16
runtime.main
C:/Go/src/runtime/proc.go:200
runtime.goexit
C:/Go/src/runtime/asm_amd64.s:1337
一番最初に記載した公式ドキュメントからの引用だと、+
はフィールド名を出力するだけでした。しかし、pkg/errors では +
を付与するとスタックトレースが出力されています。
これはなぜかというと pkg/errors で Format メソッドを独自に実装しているためです。以下は pkg/errors の errors.go からの抜粋です。
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
}
}
書式のフラグの有無に応じて出力内容を分岐させていることが分かります。公式ドキュメントにあるような +
, -
, #
,
, 0
のフラグがある場合は Flag メソッドで true が return されます。
%v
のように +
の Flag がなかった場合でも、文字列であれば case 's':
の処理をします。このようにして %+v
の場合は withStack 構造体で保持しているスタックトレースの情報を出力することが分かりました。
- どのように Formatter を独自実装するのか
fmt の print.go にある Formatter インターフェースを確認すると以下のようになっています。Format(f State, c rune)
をメソッドを実装することで Formatter インターフェースが満たされます。これによって独自の書式を定義することができます。pkg/errors の構造体は Format メソッドを実装していました。
// Formatter is the interface implemented by values with a custom formatter.
// The implementation of Format may call Sprint(f) or Fprint(f) etc.
// to generate its output.
type Formatter interface {
Format(f State, c rune)
}
まとめ
- 構造体を print するときの
%v
と%+v
のデフォルトの違いは、構造体のフィールド名を付与するかしないかの違い - Format メソッドを実装することで独自の Fomatter を実装することができる
- pkg/errors では独自実装することでスタックトレースを print している