Goのinterface
Goのinterfaceはコンパイルタイムとランタイムで処理が分かれてるんだけど,その境目がよくわかってなくて,全部コンパイルタイムに移るせるんじゃないのかなぁと昨日Twitterで呟いたら,自分の勘違いとかが発覚したので,ここでinterfaceの処理を忘れないうちにまとめておく.Thanks to hajimehoshi and seagetch!
注意点として俺はGoのソースコードを走らせたこともないユーザなので,中身が本当に気になる方はソースや資料("Google Go! A look behind the scenes")を読んだ方がいいです!
interfaceの使い方
interfaceはGoでポリモーフィズム?を実現するための機能.Javaとかのinterfaceと決定的に違うのは,後付けで構造体に好きに実装できるところ.Goは継承を持ってないけど,これでポリモーフィズムが出来るようになる.
type Stringer interface { String() string; } type Binary uint64 func (i Binary) Get() uint64 { return uint64(i) } // Stringerを実装したことになる func (i Binary) String() string { return strconv.Uitob64(i.Get(), 2) } // 後は以下のように使える b := Binary(200) s := Stringer(b) // Stringerを満たしているオブジェクトは何でもOK // code from "Listing 2.2: Using interfaces in Go!"
interfaceの実装
GoはC++でのvtblみたいなものをオブジェクトが持ってない.でも
var foo Stringer if cond { foo = &Binary } else { foo = &Hex } a.String()
とかやろうとするためにはそれ相応の機能が必要で,それがinterface table(以降itable).これはInterfaceの変数が持っていて,実態は以下.
struct Iface { Itab* tab; void* data; };
そのまんま,tabがある型(Stringer with Binary)のitableを指していて,dataがランタイム時の実際の値を指している.
コンパイルタイムにやること
- ある型がinterfaceを満たすかどうかのチェックで,満たしてなければコンパイルエラー
- Stringerのランタイム型生成
- Binaryのランタイム型生成
- コードの置換(s.String() → s.tab->fun[0](s.data)みたいな)
ランタイムにやること
- Ifaceのtabとdataを代入時に動的付け替え
- tabで使うitableを生成し,Binaryのメソッドリストでitableのメソッドリストを埋める
- 結果をキャッシュ(StringerとBineryのハッシュ計算と思われる)
- "static Itab* hash[1009];"
なので最初はitableの生成で時間が掛かるけど,それ以降はキャッシュがあるので,呼び出しコストは一定になる.Itabはlinkを持っているので,ハッシュ値で被ってもitableが上書きされず当該バケットにつながれる.
まとめ
それなりに頑張ってました.コンパイルタイムとか最初に全部itableを計算しないのは,処理によっては通らないパスがあるので,無駄にitableを生成しないためっぽい.
Go使わないのに何やってんだろ…