きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

Go の struct と interface で Embedding

Effective Go の Embedding の内容を試してみます。

Go では embedding を利用して継承のようなことができますが、struct と interface の違いが今ひとつ理解できていなかったため、実際にコードを書いてコンパイラに怒られながら、どういう違いがあるのか試していきたいと思います。

確認方法

こんな感じで embedding を利用して BaseMethodSuperMethod を実装するパターンを色々見ていきます。

type baseInterface interface {
    BaseMethod() string
}

type superInterface interface {
    baseInterface
    SuperMethod() string
}

実装の確認用兼、interface を満たしているかの確認用の関数を用意します。

func printSuper(name string, v superInterface) {
    fmt.Println(name+".BaseMethod ->", v.BaseMethod())
    fmt.Println(name+".SuperMethod ->", v.SuperMethod())
}

interface を interface に埋め込む

type baseInterface interface {
    BaseMethod() string
}

// 一応 baseInterface の方の実装も作っておく
type implOfBaseInterface struct{}

func (implOfBaseInterface) BaseMethod() string {
    return "implOfBaseInterface.BaseMethod"
}

type superInterface interface {
    baseInterface
    SuperMethod() string
}

type implOfSuperInterface struct{}

// interface を interface に埋め込んだ場合 BaseMethod の定義は必要
func (i implOfSuperInterface) BaseMethod() string {
    return "implOfSuperInterface.BaseMethod"
}

func (implOfSuperInterface) SuperMethod() string {
    return "implOfSuperInterface.SuperMethod"
}

impleOfSuperInterface 側でも BaseMethod の実装が必要になります。 実装を参照する先も無いので当たり前と言えば当たり前ですね。

func main() {
    printSuper("implOfSuperInterface", implOfSuperInterface{})
}

してみると

implOfSuperInterface.BaseMethod -> implOfSuperInterface.BaseMethod
implOfSuperInterface.SuperMethod -> implOfSuperInterface.SuperMethod

これも impleOfSuperInterface に実装したものがそのまま呼ばれて、当たり前の結果ですね。 このパターンの埋め込みをするメリットとしては、interface 側のメソッドの定義を省略できることくらいでしょうか。

struct を struct に埋め込む

type baseStruct struct{}

func (b baseStruct) BaseMethod() string {
    return "baseStruct.BaseMethod"
}

type superStruct struct {
    baseStruct
}

// この場合 BaseMethod は省略できるが実装してもいい
// func (s superStruct) BaseMethod() string {
//     // 明示的に baseStruct.BaseMethod を呼ぶこともできるし(あまり意味なさそう)
//     return s.baseStruct.BaseMethod()
//     // 他の処理を定義してもいい
//     return "superStruct.BaseMethod"
// }

func (s superStruct) SuperMethod() string {
    return "superStruct.SuperMethod"
}

struct を struct に埋め込んだ場合は superStruct.BaseMethod の実装を省略できるみたいです。

func main() {
    printSuper("superStruct", superStruct{})
}

してみると

superStruct.BaseMethod -> baseStruct.BaseMethod
superStruct.SuperMethod -> superStruct.SuperMethod

superStruct.BaseMethodbaseStruct.BaseMethod が呼ばれていますね。

struct を interface に埋め込む(不可)

type structEmbeddedInterface interface {
    baseStruct
    SuperMethod()
}

type implOfStructEmbeddedInterface struct{}

func (implOfStructEmbeddedInterface) SuperMethod() {
    fmt.Println("structEmbeddedInterface.SuperMethod")
}

これは不可能なパターンで、コンパイル時に interface contains embedded non-interface baseStruct と怒られます。

interface を struct に埋め込む

type interfaceEmbeddedStruct struct {
    baseInterface
}

func (interfaceEmbeddedStruct) SuperMethod() string {
    return "interfaceEmbeddedStruct.SuperMethod"
}

この場合は interfaceEmbeddedStruct.BaseMethod の実装をしなくてもコンパイルは通ります。しかし、ランタイムで panic: runtime error: invalid memory address or nil pointer dereference というエラーになります。一番タチが悪いですね。

このパターンの活用事例は悩ましいですが、考察されているページがあったので時間のある時に読みたいと思います。 https://horizoon.jp/post/2019/03/16/go_embedded_interface/

今回触って改めて、このGo言語における embedding は手を動かさないと理解できないなと思いました。単純にパターンが多いので網羅しづらいのと、網羅されても今度は長大になるため読み解く時間がないので、手元で試した方が速いなと…