はじめに
この記事は Go7 Advent Calendar 2019 の 18 日目の記事です。
Go さん nil さんの気持ちをもうちょっと理解してみようと思ったところを発端にソースコードを読み始めた記録です。
見返してみたらとても当たり前なことしか述べておりませんでした (´·ω·`)
本記事は go 1.13
を前提にしています。
TL;DR
- nil 周りのソースコード読んでみた
- nil って何?という話と nil っぽい何かが 2 つ見つかったけど何?という話
- 目新しい話はありません(´·ω·`)
nil の登場
nil らしきものの姿は src/go/types/universe.go にて見つけることができます。https://golang.org/ref/spec#Blocks にも記載のある universe block
がスコープになっています。
ただ、その近辺でも nil
が使われていたりしてよくわかりません。
もう少し追ってみると、このあたり(builtin パッケージ) で下記のように定義されています。
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
...
type Type int
これは、
/*
Package builtin provides documentation for Go's predeclared identifiers.
The items documented here are not actually in package builtin
but their descriptions here allow godoc to present documentation
for the language's special identifiers.
*/
とある通り、ドキュメンテーションであってどこからも参照されていません。
これをそのまま受け取ると、 nil は Type 型の初期値
というように理解できます。
なんとなく外れていなさそうな雰囲気です。
初期値とは
では Type 型の初期値
とは一体どなた様でしょうか。ふんいきしかわからない。
コンパイラ側のソースコードを読んでみると、こちらのコードに辿り着きました。
// zeroVal returns the zero value for type t.
func (s *state) zeroVal(t *types.Type) *ssa.Value {
switch {
...
case t.IsPtrShaped():
return s.constNil(t)
case t.IsBoolean():
return s.constBool(false)
case t.IsInterface():
return s.constInterface(t)
case t.IsSlice():
return s.constSlice(t)
case t.IsStruct():
n := t.NumFields()
v := s.entryNewValue0(ssa.StructMakeOp(t.NumFields()), t)
for i := 0; i < n; i++ {
v.AddArg(s.zeroVal(t.FieldType(i)))
}
return v
...
前述の builtin
パッケージには、 var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
とありますので、これを素直に受け取ると
case t.IsPtrShaped():
return s.constNil(t)
で、 s.constNil(t)
が正体のように見えます。
二つの nil
話は nil らしきものの姿 src/go/types/universe.go に戻ります。こちら、見てみると
var Typ = []*Basic{
...
UntypedNil: {UntypedNil, IsUntyped, "untyped nil"},
...
}
...
func defPredeclaredTypes() {
for _, t := range Typ {
def(NewTypeName(token.NoPos, nil, t.name, t))
}
...
と、
func defPredeclaredNil() {
def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}})
}
の、nil っぽい何かが 2 つあるようです。
これらの違いは、値か型か、ということがコードからわかります。
func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool) {
...
switch obj := obj.(type) {
...
case *TypeName:
x.mode = typexpr
...
case *Nil:
x.mode = value
...
UntypedNil
は、例えば、
var x = nil
// use of untyped nil
とすると会うことができます。
他にも、下記のようにしても会えます。
var x interface{}
x = nil // <- untyped nil の代入
interface だけ ok なのは、このあたりのコード に記述されています。
case *Interface:
if !x.isNil() && !t.Empty() /* empty interfaces are ok */ {
goto Error
}
// Update operand types to the default type rather then
// the target (interface) type: values must have concrete
// dynamic types. If the value is nil, keep it untyped
// (this is important for tools such as go vet which need
// the dynamic type for argument checking of say, print
// functions)
if x.isNil() {
target = Typ[UntypedNil]
} else {
// cannot assign untyped values to non-empty interfaces
if !t.Empty() {
goto Error
}
target = Default(x.typ)
}
おわりに
その他、いろいろ読んでみて、あ!これ前にXXでやったやつだ!みたいな発見も結構あったのですがただでさえまとまっていない内容がさらにまとまらなくなるので割愛します。
今までよりほんのちょっとだけ Go さん nil さんの気持ちがわかったような気がしました。