Goの fmt パッケージは、%s
や %3.4f
のような書式を使えます。基本的にはCと同じですが、%#v
や %T
などGo固有なものもあります。書式は基本的に
%[flag][width][.precision]{verb}
のように分かれていて、それぞれの数値は幅や0埋めなどを指定します。{verb}
は必須ですが、それ以外は省略可能です。
func main() { fmt.Printf("%+07.*f\n", 3, 2.3) // Output: +02.300 }
詳細な仕様は 公式ドキュメントの fmt パッケージに書かれています。
ポインタを整形したい
ただしポインタの場合、%d
や%x
を使ってもポインタの値をフォーマットするだけです。ポインタが参照する先の値を扱うわけではありません。
func main() { i := 10 p := &i fmt.Printf("%[1]p = %[1]d\n", p) // Output: 0xc000018088 = 824633819272 }
なのでポインタの場合に値をフォーマットしたい場合は *p
のように参照しなければならないのですが、nil
を参照するとパニックするので、分岐をしなければなりません。
func main() { i := 10 p := &i if p == nil { fmt.Printf("<nil>\n") } else { fmt.Printf("%p = %d\n", p, *p) } // Output: 0xc00006e008 = 10 }
分岐を何度も書くのは良くないし、2つ以上の値を同時に出力しようとすると一気に複雑化するので、カスタムフォーマッタでうまく扱えないかと思いました。
fmt.Stringer を実装する
fmt.Println
や%v
、%s
でフォーマットする場合、fmt.Stringer を実装しておくとそれが使われるようになります。また、%#v
の場合は fmt.GoStringer があれば使われます。
type IntPtr struct { v *int } func (p IntPtr) String() string { if p.v == nil { return "<nil>" } return fmt.Sprintf("%d", *p.v) } func main() { p0 := IntPtr{nil} i := 10 p1 := IntPtr{&i} fmt.Printf("%v %v\n", p0, p1) // Output: <nil> 10 }
ただし、この場合は %04x
などの書式を扱うことができません。
fmt.Formatter を実装する
%d
などでフォーマットする場合、型が fmt.Formatter を実装していれば、それが使われるようになります。fmt.Stringer と異なり、引数でfmt.Stateを受け取るので、これを使って整形ができます。また、fmt.State は io.Writer を実装しているので、fmt.Fprintf
の出力先として使えます。
type IntPtr struct { v *int } func (p IntPtr) Format(f fmt.State, c rune) { if p.v == nil { fmt.Fprintf(f, "<nil>") return } format := "%" if f.Flag('0') { format += "0" } if wid, ok := f.Width(); ok { format += fmt.Sprintf("%d", wid) } if prec, ok := f.Precision(); ok { format += fmt.Sprintf(".%d", prec) } format += string(c) fmt.Fprintf(f, format, *p.v) } func main() { i := 10 p := IntPtr{&i} fmt.Printf("%[1]d %04[1]x\n", p) // Output: 10 000a }
なぜIntPtrを構造体にしているか
Go言語仕様で、メソッドレシーバの基本型(base type)にポインタやインターフェイスを使えません。基本型というのは、メソッドのレシーバ型は T
または *T
が使えるけれども、両方における T
のことです。これはMethod declarationsに書かれています。
Its type must be of the form T or *T (possibly using parentheses) where T is a type name. The type denoted by T is called the receiver base type; it must not be a pointer or interface type and it must be defined in the same package as the method.
なので、
type IntPtr *int func (p IntPtr) String(f fmt.State, c rune) { }
上のコードは、
invalid receiver type IntPtr (IntPtr is a pointer type)
というエラーでコンパイルできません。これはRe: named pointer type: invalid receiver typeによると、以下のような場合にどちらのGet
を使えばいいのか明確ではないため、だそうです。
type I int func (i I) Get() int { return int(i) } type P *I func (p P) Get() int { return int(*p) } var v I var x = (&v).Get()