この記事はフラー株式会社 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.Print
がa
の実行時の型をみて、空のスライスである[]
を出力したためです。
こう書くと、当たり前のような感じもしてきます。
これだけだと味気ないため、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 == nil
がfalse
を返すためです。
ただし、この挙動は仕様に明記されていないため、厳密さに欠く部分があります。以上の結論は、こちらの記事を参考に出しました。
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 []int
のa
のvalue.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さんの記事です。それでは。