0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-12-20

この記事はフラー株式会社 Advent Calendar 2022の21日目の記事です。

前書き

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

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

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

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

ネタバレ

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

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

トップレベルから

ソースコードはこちら

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

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 == nilfalseを返すためです。

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

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が呼び出されます。

	// 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 []intavalue.Kind()reflect.Sliceであるため、空のスライス([]int{})をfmt.Printに渡した場合と同じ出力結果になります。

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さんの記事です。それでは。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0