スライスのゼロ値を Print すると [] が出力される理由を探ってみた

この記事は[フラー株式会社 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fuller-inc)の21日目の記事です。

## 前書き

以下を実行すると ```<nil>```が出力されます。

```go
func main() {
	fmt.Print(nil)　// -> <nil>
}
```

ところで、[Goの言語仕様](https://go.dev/ref/spec#Slice_types)により、スライスのゼロ値は```nil```です。そのため、スライスを宣言しプリントすると、```<nil>``` が出力されると期待しますが、実際は```[]```が出力されます。この記事はその謎を追いました。

```go
func main() {
	var a []int
	fmt.Print(a) // -> []
}
```

## ネタバレ

```[]```が出力される理由は、```fmt.Print```を適用した```a```の実行時の型がスライス型であり、
```fmt.Print```が```a```の実行時の型をみて、空のスライスである```[]```を出力したためです。

こう書くと、当たり前のような感じもしてきます。
これだけだと味気ないため、```fmt.Print```の中身を掘り、```[]```が出力されるまでの過程を説明します。

## トップレベルから
[ソースコードはこちら](https://cs.opensource.google/go/go/+/refs/tags/go1.19.4:src/fmt/print.go;l=251)

```Print```では可変長引数を取れるようになっていて、```doPrint```でバラしています。
個々の出力は ```p.printArg(arg, 'v')``` で行われています。

```go
func Print(a ...any) (n int, err error) {
	return Fprint(os.Stdout, a...)
}
func Fprint(w io.Writer, a ...any) (n int, err error) {
	p := newPrinter()
	p.doPrint(a)
	n, err = w.Write(p.buf)
	p.free()
	return
}
func (p *pp) doPrint(a []any) {
	prevString := false
	for argNum, arg := range a {
		isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String
		// Add a space between two non-string arguments.
		if argNum > 0 && !isString && !prevString {
			p.buf.writeByte(' ')
		}
		p.printArg(arg, 'v')　
		prevString = isString
	}
}
```
## p.printArg(arg, 'v') から
以下の```if```ブロックに遷移すると、```nilAngleString```(=```<nil>```) が出力され処理が終わります。ここで、```if```に遷移しない理由ですが、```nil```の型が```any```である一方、```arg```の(動的な)型が```[]int```であるため、型が一致せず、```arg == nil```が```false```を返すためです。

ただし、この挙動は[仕様](https://go.dev/ref/spec#Comparison_operators)に明記されていないため、厳密さに欠く部分があります。以上の結論は、こちらの記事を参考に出しました。
https://forum.golangbridge.org/t/a-nil-a-b-b-nil-with-pointers-and-interface/10593

```go
func (p *pp) printArg(arg any, verb rune) {
	p.arg = arg
	p.value = reflect.Value{}

	if arg == nil {
		switch verb {
		case 'T', 'v':
			p.fmt.padString(nilAngleString) // verb は 'v' なのでこちらに遷移する
		default:
			p.badVerb(verb)
		}
		return
	}
...
```

## p.printArg(arg, 'v') 続き
```arg```の型情報をチェックし遷移します。```[]int``` はどの型にも当てはまらないため、```default```に遷移し、最終的に```p.printValue```が呼び出されます。

```go
	// Some types can be done without reflection.
	switch f := arg.(type) {
	case bool:
		p.fmtBool(f, verb)
	case float32:
		p.fmtFloat(float64(f), 32, verb)
	case float64:
		p.fmtFloat(f, 64, verb)
	case complex64:
		p.fmtComplex(complex128(f), 64, verb)
	case complex128:
		p.fmtComplex(f, 128, verb)
	case int:
		p.fmtInteger(uint64(f), signed, verb)
	case int8:
		p.fmtInteger(uint64(f), signed, verb)
	case int16:
		p.fmtInteger(uint64(f), signed, verb)
	case int32:
		p.fmtInteger(uint64(f), signed, verb)
	case int64:
		p.fmtInteger(uint64(f), signed, verb)
	case uint:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint8:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint16:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint32:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint64:
		p.fmtInteger(f, unsigned, verb)
	case uintptr:
		p.fmtInteger(uint64(f), unsigned, verb)
	case string:
		p.fmtString(f, verb)
	case []byte:
		p.fmtBytes(f, verb, "[]byte")
	case reflect.Value: // fmt.Print に reflect.Value(hoge) を渡した場合
		// Handle extractable values with special methods
		// since printValue does not handle them at depth 0.
		if f.IsValid() && f.CanInterface() {
			p.arg = f.Interface()
			if p.handleMethods(verb) {
				return
			}
		}
		p.printValue(f, verb, 0)
	default:
		// If the type is not simple, it might have methods.
		if !p.handleMethods(verb) {
			// Need to use reflection, since the type had no
			// interface methods that could be used for formatting.
			p.printValue(reflect.ValueOf(f), verb, 0)
		}
	}
```

## printValueの中へ (ここで最後です）
リフレクションを用いて、型の種類から出力を出し分けています。
最初に宣言した```var a []int```の```a```の```value.Kind()```は```reflect.Slice```であるため、空のスライス(```[]int{}```)を```fmt.Print```に渡した場合と同じ出力結果になります。

```go
func (p *pp) printValue(value reflect.Value, verb rune, depth int) {
	// ...
	switch f := value; value.Kind() {
	// ...
	case reflect.Array, reflect.Slice:
		// ...
		if p.fmt.sharpV {
        // ...
		} else { // ここ！
			p.buf.writeByte('[')
			for i := 0; i < f.Len(); i++ { // f.Len() == 0 なので for文に遷移しない
				if i > 0 {
					p.buf.writeByte(' ')
				}
				p.printValue(f.Index(i), verb, depth+1)
			}
			p.buf.writeByte(']')
		}
    // ...
	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
		p.fmtPointer(f, verb)
	default:
		p.unknownType(f)
	}
}
```

## まとめ
- ```[]``` が出力される理由を知るため、fmt.Printのソースコードを追った
- fmt.Print は型情報から出力を出し分けている
- ```arg == nil```の下り、根拠を仕様から辿れるようになりたい（無理かも）

22日目は、[@Daiji256](https://qiita.com/Daiji256)さんの記事です。それでは。