はじめに
そろそろGo1.11が出そうという中、Go1.10のコードで「コンパイラを読んでGoを理解する」企画を遅々と進めているわけですが、読んでる中で以下のようなコードがありました(サンプルコードであり、コンパイラ中のコードそのものではありません)
package main
import (
"fmt"
"io"
"strings"
)
func read(r io.ByteReader) {
for {
if value, err := r.ReadByte(); err == nil {
fmt.Println(value)
} else {
fmt.Println(err)
break
}
}
}
func main() {
s := "abc"
// 型がわかるように書いているが普通は「reader := ...」でOK
var reader *strings.Reader = strings.NewReader(s)
read(reader)
}
思ったこと1:受け取り側の型違ってるけど…
→io.ByteReaderはインターフェースです。
思ったこと2:受け取り側ポインタ付けなくていいの?
→付ける必要はありません。それがインターフェースです。
というわけでこの仕組みについて説明していきます。
Goのインターフェース
まずstrings.Readerの説明を見てみましょう。
https://golang.org/pkg/strings/#Reader
A Reader implements the io.Reader, io.ReaderAt, io.Seeker, io.WriterTo, io.ByteScanner, and io.RuneScanner interfaces by reading from a string.
io.ByteReaderをimplementしてるとは書いていません。書き忘れでしょうか?
GoではJava等と異なり、インターフェースを明示的に宣言(という言葉が適切ではない気がするが「このインターフェース定義してるで」とコードに明示するという意味で)する必要はありません。インターフェースが要求する名前、引数、戻り値のメソッドが書いてあればインターフェースを満たします。
というわけでio.ByteReaderインターフェースを見てみましょう。
https://golang.org/pkg/io/#ByteReader
type ByteReader interface {
ReadByte() (byte, error)
}
確かにstrings.Readerにはこのメソッドが定義されています。というわけでstrings.Readerはio.ByteReaderインターフェースが要求される関数に渡すことができます。
https://golang.org/pkg/strings/#Reader.ReadByte
Goのメソッド
次に、引数渡す側ポインタだけど受け取り側のインターフェースはポインタにしなくていいの?という件について。これはGoのメソッドについて理解する必要があります。
ReadByteメソッドは*strings.Reader型をレシーバとしています。つまり、ポインタ型に対してReadByteメソッドが定義されています。そのため、ポインタを渡すことにより、受け取り側は「実際の型はともかくio.ByteReaderは満たしてるな」とインターフェースを受け取ることができます。うーん、ここら辺の呼び出し処理の実装ややこしそう。コンパイラというよりランタイム部分になるのかな。
まとめ
今回はコードを読んでいて見かけた、Goのインターフェースの使い方について理解があやふやだったところを確認しました。Goのインターフェースは以下の特徴があります。
- 明示的に書いていなくても(そもそもコード的に明示する方法はない)要求されるメソッドさえ書いてあればいい
- 渡す側がポインタであっても受け取り側はポインタで受け取る必要はない(メソッドがポインタレシーバである必要はある)
Goな人達がよく「標準ライブラリを読むのだ。そこにGoの書き方が詰まっている」とおっしゃっていますが本当ですね(笑)