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使わないのに何やってんだろ…