fmt.Formatterを実装して%vや%+vをカスタマイズしたり、%3🍺みたいな書式をつくってみよう #golang
September 2, 2016
この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。
fmtパッケージのインタフェース
fmtパッケージにはいくつかインタフェースがあります。
例えば、ここではフォーマットに関わる以下の3つについて説明していきましょう。
Stringerインタフェース
fmt.Stringerインタフェースは有名でしょう。
定義は以下のようになっています。
type Stringer interface {
String() string
}%sや%vのフォーマットで文字列を作成する際に、このインタフェースを実装していると、Stringメソッドの実行結果が用いられます。
package main
import (
"fmt"
)
type MyString string
func (s MyString) String() string {
return "hi, MyString"
}
func main() {
s := MyString("hello")
fmt.Println(s)
fmt.Printf("%s\n", s)
fmt.Printf("%q\n", s)
fmt.Printf("%v\n", s)
fmt.Printf("%+v\n", s)
fmt.Printf("%#v\n", s)
}実行結果
hi, MyString
hi, MyString
"hi, MyString"
hi, MyString
hi, MyString
"hello"GoStringerインタフェース
Stringerインタフェースの例の実行結果を見ると、%#vだけ表示結果がカスタマイズできていません。
%#vをカスタマイズするには、fmt.GoStringerインタフェースを実装する必要があります。
fmt.GoStringerインタフェースは以下のように定義されています。
type GoStringer interface {
GoString() string
}さて、以下のサンプルを動かしてみましょう。
うまく%#vの場合もカスタマイズできていることがわかると思います。
package main
import (
"fmt"
)
type MyString string
func (s MyString) String() string {
return "hi, MyString"
}
func (s MyString) GoString() string {
return "Yeah! hello %#v!"
}
func main() {
s := MyString("hello")
fmt.Println(s)
fmt.Printf("%s\n", s)
fmt.Printf("%q\n", s)
fmt.Printf("%v\n", s)
fmt.Printf("%+v\n", s)
fmt.Printf("%#v\n", s)
}実行結果
hi, MyString
hi, MyString
"hi, MyString"
hi, MyString
hi, MyString
Yeah! hello %#v!Formatterインタフェース
それでは、%sのsを自作したい場合やもっと複雑な処理がしたい場合はどうするべきでしょうか?
そういう場合は、fmt.Formatterインタフェースを実装してやります。
ちょうど良い例として、pkg/errorsパッケージが参考になるでしょう。
以下のコードを見るだけでも、vやsの挙動を変えていることが、なんとなくわかるかと思います。
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}さて、fmt.Formatterインタフェースの定義を改めて見てみましょう。
type Formatter interface {
Format(f State, c rune)
}Formatというメソッドを実装する必要が分かります。
さて、このメソッドのf Stateとc runeという引数はそれぞれ何を表すのでしょうか?
pkg/errorsパッケージの例を見ると、第2引数のc runeは%sや%vのsやvに当たる文字だということが分かります。
それでは、f Stateは一体何者でしょうか?
まずは定義を見てましょう。
type State interface {
// Write is the function to call to emit formatted output to be printed.
Write(b []byte) (n int, err error)
// Width returns the value of the width option and whether it has been set.
Width() (wid int, ok bool)
// Precision returns the value of the precision option and whether it has been set.
Precision() (prec int, ok bool)
// Flag reports whether the flag c, a character, has been set.
Flag(c int) bool
}Stateもインタフェースであることが分かります。
ここで注目してほしいのは、Writeメソッドを提供していることです。
Stateインタフェースを実装していれば、io.Writerインタフェースも実装している事になります。
つまり、Stateインタフェースはio.Writerを引数に取るfmt.Fprintfなどの関数に渡せるということになります。
察しが良い方は、お気づきかと思いますが、Formatメソッドには戻り値が無いので、StringメソッドやGoStringメソッドのように、カスタマイズしたフォーマットの適用結果を戻り値として返すことができません。
代わりに、Stateに対して書き込みを行うことでフォーマットの適用結果を反映させています。
続いて、他のStateインタフェースのメソッドについても見てましょう。
Widthメソッドは%2dのように幅を指定した際に、その幅を取得するためのメソッドのようです。
幅が指定されていない場合は、第2戻り値がfalseになるようです。
Precisionメソッドは%.3fなどのように、精度を指定した場合にその値が取得できるメソッドです。
こちらも同様に指定されていない場合は、第2戻り値がfalseになります。
最後にFlagメソッドは、引数に渡したフラグ+や#が設定されている場合はtrueを返すメソッドです。
Flag('+')は、%vの場合はfalseで%+vの場合はtrueを返します。
それでは、これらを踏まえて以下のサンプルを動かしてみましょう。
c runeには、🍺などの絵文字も書けるので、%🍺も使えるようになります!
幅や精度もうまく指定できていることが分かります。
package main
import (
"fmt"
"strings"
)
type MyString string
func (s MyString) Format(f fmt.State, c rune) {
if c == '🍺' && f.Flag('+') {
var beers string
if w, ok := f.Width(); ok {
beers = strings.Repeat("🍺", w)
} else {
beers = "no beer!"
}
var stars string
if p, ok := f.Precision(); ok {
stars = strings.Repeat("🌟", p)
}
fmt.Fprintf(f, "%s%s", beers, stars)
return
}
fmt.Fprintf(f, "%c %v %v", c, f.Flag('+'), f.Flag('#'))
}
func main() {
s := MyString("Hello, playground")
fmt.Printf("%v\n", s)
fmt.Printf("%+v\n", s)
fmt.Printf("%#v\n", s)
fmt.Printf("%#+s\n", s)
fmt.Printf("%+3.2🍺\n", s)
}v false false
v true false
v false true
s true true
🍺🍺🍺🌟🌟まとめ
fmt.Formatを使えば、複雑なフォーマットを自分で作れることを説明しました。
ぜひ、デバッグに役立つフォーマットを作ってみてください。