Go interfaceの理解しにくいところ

※ Retty Advent Calendar 2018 11日目の記事です。
昨日は @shogo-tsutsumi さんの記事で、 [広告KPIダッシュボード開発でPDCA回してみた](https://qiita.com/shogo-tsutsumi/items/b6f8a2b45adbe2d7b110) でした。

# はじめに
RettyではPHP・Kotlinなど様々な言語を使って開発を行なっています。
その中、今年Goを使ったマイクロサービスの開発も始まりました。
今回、個人的にGoの機能の中でも一番有能なinterfaceについて書きます。

# GoのDuck Typingについて
Python, Rubyなどのdynamic言語でよく知られているDuck Typingですが、
Javaみたいに`implements`キーワードを使ってinterfaceの実装を宣言しなくても、
runtime時に、methodが実装されているかどうかをチェックしてくれる便利な仕組みです。

GoではPython, Rubyのような単純なDuck Typingよりもっと便利な実装になっています。
まず、**runtime**時にdynamicチェックが行われます。

```go:dynamic-check.go
... // import略
type I interface {
	PrintName()
}

type T struct{}

func (*T) PrintName() {
	fmt.Println("T")
}

func p(i interface{}) {
	i.(I).PrintName()
}

func main() {
	var t T
	p(&t)
	p(t) // panic: interface conversion: main.T is not main.I: missing method PrintName
}
```
[playground](https://play.golang.org/p/TpcT3EOnGIJ)
そして、staticでチェック可能なものは**compile**時staticチェックもしてくれます。

```go:static-check.go
... // import&declare略
func p(i I) { // interface{} -> I に変わったことで、compile時にチェックが可能になる
	i.(I).PrintName()
}

func main() {
	var t T
	p(&t)
	p(t) // cannot use t (type T) as type I in argument to p
}
```
[playground](https://play.golang.org/p/OL3pfb0FXo9)

# 本題に
compile時とruntime時でどちらもtype checkが行われる、
その便利なGo interfaceですが、割と理解しにくいところもあります。

### まず、サンプルコードから
```go:
... // import略
type I interface {
	PrintName()
}

type T1 struct{}

func (t *T1) PrintName() { // pointer receiver
	fmt.Println("T1")
}

type T2 struct{}

func (t T2) PrintName() { // value receiver
	fmt.Println("T2")
}

func main() {
	var i I

	var pT1 *T1
	i = pT1
	i.PrintName()

	var t2 T2
	i = t2
	i.PrintName()
}
```
[playground](https://play.golang.org/p/58oUPLkhuHs)
上のコードは問題なく動きます。
そして、次のコード

```go:
... // import&declare略
func main() {
	var i I

	//var t1 T1
	//i = t1 // cannot use t1 (type T1) as type I in assignment
	//i.PrintName()

	var pT2 *T2
	i = pT2
	i.PrintName() // panic: value method main.T2.PrintName called using nil *T2 pointer
}
```
[playground](https://play.golang.org/p/35xPR9EoWYX)

まず、`t1`は`I`を実装していないため、`i = t1`compileエラー、とても分かりやすいです。
でも、`pT2`は`I`を実装してないはずだけど、なんで`i = pT2`compile通る？とても分かりません。
さらに、compile通ったけど、なんで`pT2`の`i.PrintName()`がruntimeエラーになる？？？もっと分かりません。

### なんで`i = pT2`ではcompileが通る？
色々調べた結果、gccgoのソースコードの中から理由を見つけました。
[libgo/runtime/interface.h#15](https://chromium.googlesource.com/native_client/nacl-gcc/+/ng/4.9/master/libgo/runtime/interface.h#15)
[libgo/runtime/go-convert-interface.c#24](https://chromium.googlesource.com/native_client/nacl-gcc/+/ng/4.9/master/libgo/runtime/go-convert-interface.c#24)
gccgoのinterfaceの定義とinterface(gccgoの中ではstructもinterface扱い)間のコンバートを読んで理解しました。

* interface(`__go_interface `)がtype descriptor(`__go_type_descriptor`)で表現される
* interfaceのtype descriptorが3つの要素を持つ
    * typeのpointer  -> `I`のpointer
    * function pointerのリスト  -> `PrintName`のfuntion pointerが入ってるリスト
    * valueのpointer  -> **重要**: ここは`pT2`のpointerなはずなんだけど、**gccgoはheap領域をケチるため、どうやらpointerのpointerは保存しない。なので、ここinterfaceのvalueがpointerである場合、直接valueそのものを保存する** (**謎が解けた!!**)

なので、`pT2`を`i`に代入する際に、`i`のtype descriptorが持つのは`pT2`そのものであって、`pT2`のpointerではありません。
だから、`i = pT2`は`i = *pT2`(compileは通るが、nilをdereferenceしようとするので、ここはruntimeエラーになる)と同じ意味をします。

### なんで`pT2`の`i.PrintName()`がnil pointerエラー？
```go:spec
type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T
var pt *T
```
> As with selectors, a reference to a non-interface method with a value receiver using a pointer will automatically dereference that pointer: pt.Mv is equivalent to (*pt).Mv.

> As with method calls, a reference to a non-interface method with a pointer receiver using an addressable value will automatically take the address of that value: t.Mp is equivalent to (&t).Mp.

Goの[spec](https://golang.org/ref/spec)に載せてる上の英語を読んでみればすでに分かったと思いますが、一応説明します。

* methodのreceiverがvalueの場合、pointerが自動的にdereferenceされる
    * `pt.Mv`は自動的に`(*pt).Mv`にされる
* methodのreceiverがpointerの場合、valueのアドレスが自動的に取得される
    * `t.Mp`は自動的に`(&t).Mp`にされる

なので、`i.PrintName()`を呼ぶと、interface`i`が持っているvalue`pT2`がreceiverとしてfunction`PrintName`を呼び出します。
つまり、`pT2.PrintName()`、上のルールで自動的に`(*pT2).PrintName()`にされます。
`pT2`がnilな値なので、deferenceしようとすると、runtimeエラーが発生します。
(**謎が解けた!!**)

# おわりに
Go interfaceはこんなちゃんと理解しようとすると、わりと理解しにくいところもあるけれど、
普段開発で使う分では全く困らないので、これからもGoをたくさん使って、もっと発信していこうと思います。
最後に、gccgoのソースコードが想定よりもとっても簡単で分かりやすかったので読んでて楽しかったです。
もし興味あれば、ぜひ読んでみてください。