Go言語 構造体をfmt.Printfで表示するときの書式の不思議

# 概要

変数の値を表示するのに`fmt.Println()`や`fmt.Printf()`をよく使います。自分で定義した構造体ならその構造は分かるのですが、packageで定義されている構造体だと良くわからないし、インタフェースとして使われていると元がどの構造体か分かりません。そこで、`fmt.Printf`の書式の違いによって表示がどう異なるか調べてみました。最後に`reflect.Type`インターフェースの構造体を表示しています。


# 基本型のみの構造体

```go:main.go
package main

import (
	"fmt"
)

type myStruct struct {
	field1 string
	field2 int
	field3 uint
	field4 *uint
	field5 float64
}

func main() {
	data := uint(987)
	val := &myStruct{"abcd", 123, 456, &data, 7.0}
	fmt.Printf("(%%#v) %#v\n", val)
	fmt.Printf("(%%+v) %+v\n", val)
	fmt.Printf("(%%v)  %v\n", val)
	fmt.Printf("(%%s)  %s\n", val)
	fmt.Printf("(%%x)  %x\n", val)
	fmt.Printf("(%%d)  %d\n", val)
	fmt.Printf("(%%f)  %f\n", val)
}
```

実行結果

```
$ go run main.go
(%#v) &main.myStruct{field1:"abcd", field2:123, field3:0x1c8, field4:(*uint)(0xc000070080), field5:7}
(%+v) &{field1:abcd field2:123 field3:456 field4:0xc000070080 field5:7}
(%v)  &{abcd 123 456 0xc000070080 7}
(%s)  &{abcd %!s(int=123) %!s(uint=456) %!s(*uint=0xc000070080) %!s(float64=7)}
(%x)  &{61626364 7b 1c8 c000070080 %!x(float64=7)}
(%d)  &{%!d(string=abcd) 123 456 824634179712 %!d(float64=7)}
(%f)  &{%!f(string=abcd) %!f(int=123) %!f(uint=456) %!f(*uint=0xc000070080) 7.000000}
```

- `%#v`
    - 構造体名が分かる
    - 項目名が分かる
    - 符号なしのときは16進表示
    - 符号ありのときは10進表示
    - ポインタのときはポインタの型が分かる
    - 値が整数のときはint系かfloat系か分からない
-  `%+v`
    - 項目名が分かる
    - 符号付きか符号なしか分からない
    - 値が整数のときはint系かfloat系か分からない
    - ポインタのときは16進表示
    - 値が整数のときはint系かfloat系か分からない
-  `%v`
    -  項目名が分からい以外は`%+v`と同じ
-   `%s`
    -   項目名が分からない
    -   すべての型が分かる。ただし、型の記述が無いものを`string`とみなすとする
- `%x`
    - 項目名が分からない
    - `float`系以外は型が分からない
- `%d`
    - 項目名が分からない
    - `string`,`float`系以外は型が分からない
- `%f`
    - 項目名が分からない
    - すべての型が分かる。ただし、`float64`,`float32`の区別がつかない


これらを見ると構造体を調べるには、`%#v`と`%s`の組み合わせが良さそうです。

# 複雑な型を含む場合

```go:main.go
package main

import (
	"fmt"
)

type type1 struct {
	f1 string
	f2 int
}

type myStruct struct {
	field1 map[string]int
	field2 [2]int
	field3 []uint
	field4 type1
	field5 *type1
	field6 *[2]int
	field7 *[2]*int
}

func main() {
	data1 := make(map[string]int)
	data1["ab"] = 12
	data1["cd"] = 34
	data2 := [2]int{1, 2}
	data3 := type1{"abcd", 789}
	data41 := 5
	data42 := 6
	data4 := [2]*int{&data41, &data42}
	val := &myStruct{data1, data2, []uint{3, 4}, data3, &data3, &data2, &data4}
	fmt.Printf("(%%#v) %#v\n", val)
	fmt.Printf("(%%+v) %+v\n", val)
	fmt.Printf("(%%v)  %v\n", val)
	fmt.Printf("(%%s)  %s\n", val)
	fmt.Printf("(%%x)  %x\n", val)
	fmt.Printf("(%%d)  %d\n", val)
	fmt.Printf("(%%f)  %f\n", val)
}
```

```
$ go run main.go
(%#v) &main.myStruct{field1:map[string]int{"ab":12, "cd":34}, field2:[2]int{1, 2}, field3:[]uint{0x3, 0x4}, field4:main.type1{f1:"abcd", f2:789}, field5:(*main.type1)(0xc000066400), field6:(*[2]int)(0xc000070080), field7:(*[2]*int)(0xc00005c1c0)}
(%+v) &{field1:map[ab:12 cd:34] field2:[1 2] field3:[3 4] field4:{f1:abcd f2:789} field5:0xc000066400 field6:0xc000070080 field7:0xc00005c1c0}
(%v)  &{map[ab:12 cd:34] [1 2] [3 4] {abcd 789} 0xc000066400 0xc000070080 0xc00005c1c0}
(%s)  &{map[ab:%!s(int=12) cd:%!s(int=34)] [%!s(int=1) %!s(int=2)] [%!s(uint=3) %!s(uint=4)] {abcd %!s(int=789)} %!s(*main.type1=&{abcd 789}) %!s(*[2]int=&[1 2]) %!s(*[2]*int=&[0xc000070090 0xc000070098])}
(%x)  &{map[6162:c 6364:22] [1 2] [3 4] {61626364 315} c000066400 c000070080 c00005c1c0}
(%d)  &{map[%!d(string=ab):12 %!d(string=cd):34] [1 2] [3 4] {%!d(string=abcd) 789} 824634139648 824634179712 824634098112}
(%f)  &{map[%!f(string=ab):%!f(int=12) %!f(string=cd):%!f(int=34)] [%!f(int=1) %!f(int=2)] [%!f(uint=3) %!f(uint=4)] {%!f(string=abcd) %!f(int=789)} %!f(*main.type1=&{abcd 789}) %!f(*[2]int=&[1 2]) %!f(*[2]*int=&[0xc000070090 0xc000070098])}
```

ここで面白いのは`%s`と`%f`で`field5`,`field6`,`field7`はポインタ型ですが、そのポインタが表している型の値も表示しているところです。`%s`と`%f`以外はポインタの値を表示しているだけです。
さらに、`%#v`も基本の型の場合は型表示がなかったですが、今度は型表示がされています。

# カスタムの型の場合

最初の基本の型をすべてカスタムの型にして実行します。

```go:main.go
package main

import (
	"fmt"
)

type (
	type1 string
	type2 int
	type3 uint
	type4 *uint
	type5 float64
)

type myStruct struct {
	field1 type1
	field2 type2
	field3 type3
	field4 type4
	field5 type5
}

func main() {
	data := uint(987)
	val := &myStruct{"abcd", 123, 456, &data, 7.0}
	fmt.Printf("(%%#v) %#v\n", val)
	fmt.Printf("(%%+v) %+v\n", val)
	fmt.Printf("(%%v)  %v\n", val)
	fmt.Printf("(%%s)  %s\n", val)
	fmt.Printf("(%%x)  %x\n", val)
	fmt.Printf("(%%d)  %d\n", val)
	fmt.Printf("(%%f)  %f\n", val)
}
```
```
$ go run main.go
(%#v) &main.myStruct{field1:"abcd", field2:123, field3:0x1c8, field4:(main.type4)(0xc000066080), field5:7}
(%+v) &{field1:abcd field2:123 field3:456 field4:0xc000066080 field5:7}
(%v)  &{abcd 123 456 0xc000066080 7}
(%s)  &{abcd %!s(main.type2=123) %!s(main.type3=456) %!s(main.type4=0xc000066080) %!s(main.type5=7)}
(%x)  &{61626364 7b 1c8 c000066080 %!x(main.type5=7)}
(%d)  &{%!d(main.type1=abcd) 123 456 824634138752 %!d(main.type5=7)}
(%f)  &{%!f(main.type1=abcd) %!f(main.type2=123) %!f(main.type3=456) %!f(main.type4=0xc000066080) 7.000000}
```

すべてカスタムの型の名称で表示されるため、数値系やポインタや文字列などの区別は付きますが、数値系で値が小さいとビット数までは分かりません。符号の有無と`float`系ということは分かりそうです。

# 構造体がString()stringのメソッドを持っている場合

```go:main.go
package main

import (
	"fmt"
)

type myStruct struct {
	field1 string
	field2 int
	field3 uint
	field4 *uint
	field5 float64
}

func (m *myStruct) String() string {
	return "called String()"
}

func main() {
	data := uint(987)
	val := &myStruct{"abcd", 123, 456, &data, 7.0}
	fmt.Printf("(%%#v) %#v\n", val)
	fmt.Printf("(%%+v) %+v\n", val)
	fmt.Printf("(%%v)  %v\n", val)
	fmt.Printf("(%%s)  %s\n", val)
	fmt.Printf("(%%x)  %x\n", val)
	fmt.Printf("(%%d)  %d\n", val)
	fmt.Printf("(%%f)  %f\n", val)
}
```

```
$ go run main.go
(%#v) &main.myStruct{field1:"abcd", field2:123, field3:0x1c8, field4:(*uint)(0xc000070080), field5:7}
(%+v) called String()
(%v)  called String()
(%s)  called String()
(%x)  63616c6c656420537472696e672829
(%d)  &{%!d(string=abcd) 123 456 824634179712 %!d(float64=7)}
(%f)  &{%!f(string=abcd) %!f(int=123) %!f(uint=456) %!f(*uint=0xc000070080) 7.000000}
```

`%+v`,`%v`,`%s`,`%x`は`String()`を呼び出していますので、このときは`%#v`,`%s`の組み合わせではく`%#v`,`%f`の組み合わせで表示するのが良さそうです。同様の働きをするのは`Error() string`です。

# インターフェースの例

`reflect.TypeOf()`は`reflect.Type`というインターフェースを返します。

```go:main.go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	val := reflect.TypeOf(1)
	fmt.Printf("(%%#v) %#v\n", val)
	fmt.Printf("(%%+v) %+v\n", val)
	fmt.Printf("(%%v)  %v\n", val)
	fmt.Printf("(%%s)  %s\n", val)
	fmt.Printf("(%%x)  %x\n", val)
	fmt.Printf("(%%d)  %d\n", val)
	fmt.Printf("(%%f)  %f\n", val)
}
```

```
$ go run main.go
(%#v) &reflect.rtype{size:0x8, ptrdata:0x0, hash:0xf75371fa, tflag:0x7, align:0x8, fieldAlign:0x8, kind:0x82, alg:(*reflect.typeAlg)(0x566d30), gcdata:(*uint8)(0x4db70c), str:1059, ptrToThis:46976}
(%+v) int
(%v)  int
(%s)  int
(%x)  696e74
(%d)  &{8 0 4149441018 7 8 8 130 5664048 5093132 1059 46976}
(%f)  &{%!f(uintptr=8) %!f(uintptr=0) %!f(uint32=4149441018) %!f(reflect.tflag=7) %!f(uint8=8) %!f(uint8=8) %!f(uint8=130) %!f(*reflect.typeAlg=&{0x451100 0x402ef0}) %!f(*uint8=0x4db70c) %!f(reflect.nameOff=1059) %!f(reflect.typeOff=46976)}
```

`reflect.Type`の元は`reflect.rtype`という構造体です。`%f`で構造体の各項目の型について多くの情報が得られます。また、`reflect.rtype`は`String() string`を持っているようです。