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
を使えば、複雑なフォーマットを自分で作れることを説明しました。
ぜひ、デバッグに役立つフォーマットを作ってみてください。