Goクイズ: Type assertions

# はじめに
本記事は, [Goクイズ Advent Calender 2020](https://qiita.com/advent-calendar/2020/goquiz)の12/24用に書かれました。

昨日は, @binzumeさんの[「Goクイズ: int = float64」](https://qiita.com/binzume/items/14f73f0f5da7f409fac9)でした。

Index expressionsに関する問題を出そうと思ったのですが, 以前[Twitterで出題した問題](https://twitter.com/task4233/status/1339189806958403585?s=20)と似ている気がしたので, 急遽Type assertionsに関する問題に取り替えました。

では, 本題に入ります。

# 問題
以下のコードを実行すると, どのように出力されるでしょうか？
[Playground](https://play.golang.org/p/28BcX6Eq4S-)

```go
package main

import (
	"fmt"
)

type I interface {
	Hello()
}

type T struct{}

func (T) Hello() {
	fmt.Println("Hello!")
}

func main() {
	var i I = nil
	v, _ := i.(T)
	v.Hello()
}

```

1. compile error
2. i.(T)の代入部分でrun-time panic
3. v.Hello()の実行部分でrun-time panic
4. Hello!

# 解答と解説
<details><summary>解答と解説</summary>
<div>
回答は, `4. Hello!`です。

# TL;DR
 - 特別な形式の代入か初期化に用いられる型Tへのアサーションは, untyped bool値を追加で生成する
 - 型アサーションが保持されていない場合, 1つ目の返り値は型Tのゼロ値, 2つ目の返り値はuntyped boolのfalseとなる
 - この場合, **run-time panicは発生しない**

# 解説
まず, [型アサーション(Type assersions)](https://golang.org/ref/spec#Type_assertions)について確認します。

> For an expression `x` of interface type and a type `T`, the primary expression `x.(T)` asserts that x is not nil and that the value stored in `x` is of type `T`. The notation x.(T) is called a type assertion.

インタフェース型の式`x`と型`T`について下記の通り書かれています。

 - `x.(T)`という表記法を型アサーションと呼ぶ
 - `x.(T)`は`x`がnilではなく`x`に格納されている値が型`T`のものであることを保証する

ということは, `x.(T)`の型アサーションが実行される場合, `x`がnilでないことが保証される訳です。しかし, 本問では`i.(T)`におけるiはnilです。これはどういうことなのでしょうか？その答えは後述されている文章にあります。

> A type assertion used in an assignment or initialization of the special form

```go
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok T1 = x.(T)
```

> yields an additional untyped boolean value. The value of ok is true if the assertion holds. Otherwise it is false and the value of v is the zero value for type T. No run-time panic occurs in this case.

 - これらの, 特別な形式の代入か初期化に用いられる型アサーションは型なしのブール値を追加で生成する
 - `ok`の値はアサーションが保持されていれば`true`である
 - それ以外の場合は`false`であり, vの値は型`T`のゼロ値である
 - この場合, **run-time panicは発生しない**

すなわち, この形式で代入および初期化をした場合, run-time panicは発生せず, 1つ目の返り値に型`T`の変数が格納されます。

ここで, "この形式"というのは, 2つ目の返り値まで受けとる形式を指すと私は考えています。なぜなら, 2つ目の形式を受け取らない場合, 下記のコードになり, run-time panicとなるためです。

[Playground](https://play.golang.org/p/K4OcAc7j0zI)

```go
package main

import (
	"fmt"
)

type I interface {
	Hello()
}

type T struct{}

func (T) Hello() {
	fmt.Println("Hello!")
}

func main() {
	var i I = nil
	var v = i.(T) // panic: interface conversion: main.I is nil, not main.T
	v.Hello()
}
```

~~これを見た時に, [型アサーション(Type assertions)](https://golang.org/ref/spec#Type_assertions)の冒頭に書かれていた, 「`x.(T)`は`x`がnilではないことを保証する」という定義と矛盾するように感じました。~~

2020/12/24追記: 
良く考えたら, 2つ目の返り値にfalseが格納されており, 型アサーションは保持されていません。そのため, 冒頭に書かれていた定義は適用されないので何の問題もありませんでした。

何はともあれ, この場合型`T`という情報は保持されるので, 当然`v.Hello()`も実行できます。

結果として, `Hello!`が出力されます。


# おわりに
対策として, 2つ目の返り値を受け取った場合は, 下記の通り分岐を入れると良いと思います。

[Playground](https://play.golang.org/p/kP0JXKVUw0d)

```go
package main

import (
    "fmt"
    "log"
    "os"
)

type I interface {
    Hello()
}

type T struct{}

func (T) Hello() {
    fmt.Println("Hello!")
}

func main() {
    var i I = nil
    v, ok := i.(T)
    if !ok {
        log.Println("invalid assertion")
        os.Exit(1)
    }
    v.Hello()
}
```

2021/01/14追記:
この形式は, `nil`から`""`(empty string)に変換する際にも使えますね。Nullを含むレコードがあるときに使えそう。

[Playground](https://play.golang.org/p/cafJl2Xwa8a)

```golang
package main

import (
	"fmt"
)

func main() {
	var n interface{} = nil
	if _, ok := n.(string); !ok {
		n = ""
	}
	fmt.Println("n: ", n.(string))
	fmt.Println("n==nil: ", n == nil)
}
```

ここまでお読みいただきありがとうございました。
明日は, @tenntennさんのラスボス問題があるようです。楽しみですね～。
</div>
</details>