C++みたいなノリでGoのインターフェースとポインタを使ったところ、はまったのでメモ。

Goでインターフェースを実装したクラスのポインタを扱う

Goで以下のように、インターフェースを実装したクラスを受けたい場合があります。

type Node interface{
  ToString() string
}

func Output(l Node) {
  fmt.Println(l.ToString())
}

type NodeTest struct{
}

func (n NodeTest) ToString() string{
  return "test"
}

func main(){
  n := NodeTest{}
  Output(n)
}

関数呼び出しのたびにオブジェクトがコピーされるのは無駄なので、 インターフェースのポインタを渡すように変更します。

func Output(l *Node) {
  fmt.Println((*l).ToString())
}

func (n *NodeTest) ToString() string{
  return "test"
}

func main(){
  n := &NodeTest{}
  Output(n)
}

この場合、インターフェースのポインタは、インターフェースを実装したstructのポインタとは違うため、 関数の引数として渡すことができず、コンパイルが通りません。

そのため、インターフェースを使う場合はオブジェクトをコピーせざるを得ないように思えますが、 ちゃんとこのような場合も解決方法は存在します。

ポインタにインターフェースを実装する

上記の2番目のコードでは、NodeTest型のポインタに対してインターフェースを実装しています。
そのため、以下のようにOutput関数の引数をNode型を受けるようにしておくのが正解になります。

func Output(l Node) {
  fmt.Println(l.ToString())
}

これにより、Output関数にNodeインタフェースを実装したNodeTestのポインタがコピーして渡され、
NodeTestオブジェクト自体はコピーされずに渡されます。

ここがC++とはだいぶ違う考え方が必要なので戸惑いました。
C++では構造体にメソッドを実装し、ポインタからその構造体のメソッドを呼び出します。
そのため、あくまでポインタの指しているオブジェクトのメソッド呼び出しであり、
クラスのポインタそのものにメソッドが定義されている訳ではありません。

ですがGoのインターフェースを使った場合、前述の通り上手く動きません。
そのため、型のポインタにメソッドを定義し、それをインターフェースとして扱う必要があります。

C++ではポインタに対して別名をつけることはできますが、メソッドの実装はできませんでした。
ですがGoではポインタやint型といったほぼすべての型に対してメソッドを実装できます。

たとえば、以下のようにint型に対しても好きなメソッドを定義できます。
(一応名前は変える必要があります)

package main
import (
    "fmt"
)

type MyInt int

func (i *MyInt) Out() {
     fmt.Println(*i)
}

func main() {
     var i MyInt = 1
     (&i).Out()
}

あらゆる型に関数をつけられるというのが、C++に染まった頭からだと理解できなくて手間取りました。

このエントリーをはてなブックマークに追加