Goのnilに関するヤヤコシイ話

## 問題

以下のコードを実行するとどうなるか？

```go
package main

import "fmt"

type unit struct{}

func (u *unit) String() string { return "UNIT" }

func null() *unit { return nil }

func main() {
	var s fmt.Stringer = null()
	if s != nil {
		fmt.Println(s.String())
	}
}
```

 1. コンパイルエラー
 2. 実行時panic
 3. `UNIT`が出力される
 4. 何も起こらない

![scthinkingtime.png](https://qiita-image-store.s3.amazonaws.com/0/14550/c2dd4130-d1bd-0f93-2d03-eb68e7ad6e5a.png)

## 解答

<details>
<summary>解答</summary>
<div>
**3. `UNIT`が出力される**
</div>
</details>

  * [Go Playgroundで試す](https://play.golang.org/p/dd56t6wKTGK)

## 解説

本問題のテーマは**「Goのヤヤコシイnilの挙動」**です。要点は次の2つです。

 1. ポインタのnil値は「参照ができない」だけ
 2. インタフェースのnil値とポインタのnil値は違う

### ポインタのnil値

ポインタのnil値は「参照先が存在しない」ことを示すものであるため、参照先取得（dereference）はできない（panicが発生する）のは当然です。注意すべきなのは**「参照先取得が起こらない限りはnil値の操作は問題がない」**ということです。

本問題では、ポインタのnil値をもつ変数`x`についてのセレクタ式`x.f`の解釈が問題になっています。これについて言語規格では以下のように定めています。

> If `x` is of pointer type and has the value `nil` and `x.f` denotes a struct field, assigning to or evaluating `x.f` causes a run-time panic.<br>([Selectors][selectors])

もし「`x`が構造体のポインタであり`f`がその構造体のフィールドを指す」のであれば、`x.f`は実質的に`(*x).f`と等価であり、そのフィールドへのアクセスは「`x`の参照先取得」を要するためpanicとなります。

しかし、`f`がメソッドを指す場合は、`x.f`という式そのものは`x`の参照先取得を伴いません。また`x.f(…)`のようにメソッドを呼び出す場合についても、（ポインタレシーバである場合は）レシーバの`x`は単に引数として関数に渡されるだけなのでやはり`x`の参照先取得は起こりません。つまり、`x.f(…)`は無問題です。

※もしメソッド`x.f`が値レシーバである場合は、関数に渡されるのは参照先取得した`*x`となるため、`x.f(…)`はpanicになります。

### インタフェースのnil値

明確な記述がなくてアレなんですが、言語規格において、「インタフェースのnil値[^nil-i-value]」は「ポインタのnil値」とは（また「その他の種類」の型のnil値」とも）区別されています。

> Variables of interface type also have a distinct dynamic type, which is the concrete type of the value assigned to the variable at run time (unless the value is the predeclared identifier nil, which has no type). The dynamic type may vary during execution but values stored in interface variables are always assignable to the static type of the variable.<br>([Variables][variables])

ここでは、インタフェースのnil値は動的型を持たないと述べられていて、この点でポインタのnil値とは性質が異なることがわかります。また、この文には以下の例示コードが続いています。

```go
var x interface{}  // x is nil and has static type interface{}
var v *T           // v has value nil, static type *T
x = 42             // x has value 42 and dynamic type int
x = v              // x has value (*T)(nil) and dynamic type *T
```

1行目は「`x`がインタフェースのnil値」をもつ場合で、これについては「`x`はnilである」と書かれています。これに対して「`x`が`*T`型のポインタのnil値」をもつ4行目では「`x`は値`(*T)(nil)`をもち動的型`*T`をもつ」と書かれています。先の引用文にある規定より「`x`がnilであるならば動的型をもたない」はずなので、結局この場合は「`x`はnil​**ではない**」と解釈するしかありません。つまり、言語規格では、「インタフェース型の変数`x`がポインタのnil値`(*T)(nil)`をもつ」場合は「`x`はnil値をもたない」（あるいは「`x`はnilではない」）と扱われるのです。要するに**「インタフェース型の話をするときにはポインタのnil値はnilではない」**のです。

### 詳細

以上の要点を踏まえて`main()`の実行を追ってみます。

```go:1行目
	var s fmt.Stringer = null()
```

`null()`は`*unit`型のnil値を返します。上の文は以下のものと同値です。

```go
var s fmt.Stringer = (*unit)(nil)
```

つまり`s`には`*unit`型の「ポインタのnil値」が入ります。

```go:2行目
	if s != nil {
```

インタフェース型の等価比較は次のように決められています。

> Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value `nil`.<br>([Comparison operators][comparison])

つまり「①両者が同一の動的型と等価な動的値をもつ」または「②両者がnil値をもつ」場合です。従って、`s == nil`の比較では両方ともnilであるため②に該当する……**のではありません！** 先ほど述べた通り、**「インタフェース型の話をするときにはポインタのnil値はnilではない」**ので左辺の`s`は「nilではない」ことになります。（右辺の`nil`は「インタフェースのnil値」なので「nilである」ことになります。）従って②は成立しません。①については、右辺の`nil`（インタフェースのnil値）は動的型をもたないので成立しません。結果、`s == nil`の等価比較は**偽になります**。

従って、`if s != nil {…}`の条件は成立することになるためブロックの中が実行されます。

```go:3行目
		fmt.Println(s.String())
```

`s.String()`というインタフェースのメソッド呼出について考えてみます。インタフェースに対するセレクタ式について次の規定があります。

> If `x` is of interface type and has the value `nil`, calling or evaluating the method `x.f` causes a run-time panic.<br>([Selectors][selectors])

しかしこれまで散々述べた通り、「`s`は**nilではない**」のでこれは適用されません。

> For a value `x` of type `I` where `I` is an interface type, `x.f` denotes the actual method with name `f` of the dynamic value of `x`. If there is no method with name `f` in the method set of `I`, the selector expression is illegal.<br>([Selectors][selectors])

`s`の動的型は`*unit`なので、この規定に従い、`s.String()`は結局`(*unit)(s).String()`、つまりは`(*unit)(nil).String()`に帰着されます。unitには実際にポインタレシーバの`String`メソッドが存在します。

```go
func (u *unit) String() string { return "UNIT" }
```

レシーバの値はポインタのnil値ですが、先に述べた通り何も問題はなく、この`String`メソッドが`u`をnilとして呼ばれます。関数定義の中でも`u`は一度も参照されていないため、何の問題も起こらず、`"UNIT"`が返ってきます。

すなわち、`s.String()`の値は`"UNIT"`であるため、`UNIT`が出力されてプログラムは終了します。

## まとめ

 1. ポインタのnil値は「参照ができない」だけ
 2. インタフェースのnil値とポインタのnil値は違う

**Goのnilはヤヤコシイ**:frowning2:


[^nil-i-value]: 「nilインタフェース値(nil interface value)」という語は規格書の中で1度だけ登場します。
[selectors]: https://golang.org/ref/spec#Selectors
[variables]: https://golang.org/ref/spec#Variables
[comparison]: https://golang.org/ref/spec#Comparison_operators