nil について調べ始めた話


## はじめに

この記事は Go7 Advent Calendar 2019 の 18 日目の記事です。
Go さん nil さんの気持ちをもうちょっと理解してみようと思ったところを発端にソースコードを読み始めた記録です。
見返してみたらとても当たり前なことしか述べておりませんでした (´·ω·`)

本記事は `go 1.13` を前提にしています。

## TL;DR

- nil 周りのソースコード読んでみた
- nil って何？という話と nil っぽい何かが 2 つ見つかったけど何？という話
- 目新しい話はありません(´·ω·`)

## nil の登場

nil らしきものの姿は [src/go/types/universe.go](https://github.com/golang/go/blob/cd016af6b6bbbeeb1c14fea574914862a50c3ad6/src/go/types/universe.go) にて見つけることができます。[https://golang.org/ref/spec#Blocks](https://golang.org/ref/spec#Blocks) にも記載のある `universe block` がスコープになっています。
ただ、その近辺でも `nil` が使われていたりしてよくわかりません。

もう少し追ってみると、[このあたり(builtin パッケージ)](https://github.com/golang/go/blob/f947c4dcfe6f22e6c92e6e5a170096b518a18448/src/builtin/builtin.go#L101) で下記のように定義されています。

```golang
    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 型の初期値` とは一体どなた様でしょうか。ふんいきしかわからない。
コンパイラ側のソースコードを読んでみると、[こちらのコード](https://github.com/golang/go/blob/8f296f59de0703b0559474beb434a265e277bdca/src/cmd/compile/internal/gc/ssa.go#L2797-L2860)に辿り着きました。

```golang

    // 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` とありますので、これを素直に受け取ると

```golang
    case t.IsPtrShaped():
    		return s.constNil(t)
```

で、 `s.constNil(t)` が正体のように見えます。

## 二つの nil

話は nil らしきものの姿 [src/go/types/universe.go](https://github.com/golang/go/blob/cd016af6b6bbbeeb1c14fea574914862a50c3ad6/src/go/types/universe.go) に戻ります。こちら、見てみると

```golang

    var Typ = []*Basic{
    ...
    UntypedNil:     {UntypedNil, IsUntyped, "untyped nil"},
    ...
    }
    ...
    func defPredeclaredTypes() {
    	for _, t := range Typ {
    		def(NewTypeName(token.NoPos, nil, t.name, t))
    	}
    ...
```

と、

```golang

    func defPredeclaredNil() {
    	def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}})
    }
```

の、nil っぽい何かが 2 つあるようです。

これらの違いは、値か型か、ということが[コード](https://github.com/golang/go/blob/553237aa67a3d0624715973a2dd3e4963e61995d/src/go/types/typexpr.go#L84)からわかります。

```golang

    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` は、例えば、

```golang

    var x = nil
    // use of untyped nil
```

とすると会うことができます。

他にも、下記のようにしても会えます。

```golang

    var x interface{}
    x = nil // <- untyped nil の代入
```

interface だけ ok なのは、[このあたりのコード](https://github.com/golang/go/blob/6fa1336531b0d1dac2b18123d74bcec4e0defdd9/src/go/types/expr.go#L550-L568) に記述されています。

```golang

    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 さんの気持ちがわかったような気がしました。