LoginSignup
20
5

More than 3 years have passed since last update.

GoのFormatterの書式における'+'フラグと独自実装

Last updated at Posted at 2019-12-09

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 からの抜粋です。

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 メソッドを実装していました。

fmt/print.go
// 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 している
20
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
5