Golang: 構造体がインタフェースを実装しているかコンパイル時にチェックする

構造体がインタフェースを実装しているかコンパイル時にチェックする話。公式のFAQ [1] や Effective Go [2] ではすでに説明されているが、知らなかったのでメモ。

go1.0.3 を想定。以下のような Animal インタフェースがあるとする。

type Animal interface {
    Bark()
    Move()
}

Animal を実装するためには、Animalで定義されているメソッドの集合、Bark()とMove() を実装する必要がある。例えば、Cat という独自の型 (構造体) を定義して、それが Animal を実装するとする。

type Cat struct {}

Move() メソッドの実装を忘れて、Bark() メソッドだけを呼び出す場合

このとき、もし Bark() だけを実装して昼ご飯に行って、うっかりMove() の実装を忘れたする。そして、Bark() だけを実際のコードで呼び出したとする:

package main

import "fmt"

type Animal interface {
    Bark()
    Move()
 }

type Cat struct{}

func (_ *Cat) Bark() { fmt.Printf("にゃーん\n") }

func main() {
    var c Cat
    c.Bark()
}

link: http://play.golang.org/p/t7TZfQeGMx

上のコードをリンク先で実行してみると分かるが、Goのコンパイラは、型CatがメソッドMove() の実装をし忘れたにもかかわらず、エラーを起こすことなく正常に終了する。

未定義のMove()メソッドを呼び出した場合

では、上記のコードのmain関数でMove() を呼び出してみるとどうなるか?

package main

import "fmt"

type Animal interface {
    Bark()
    Move()
}

type Cat struct{}

func (_ *Cat) Bark() { fmt.Printf("にゃーん\n") }

func main() {
    var c Cat
    c.Bark()
    c.Move() // error
}

http://play.golang.org/p/AWW92b7-Jq リンク先のコードを実行してみるとコンパイルエラーが発生して、以下のようなメッセージが表示される。

prog.go:17: c.Move undefined (type Cat has no field or method Move)

今回の例ではインタフェースとCat、Move() の呼び出す関数 (main.main) が同じパッケージ内にあり、インタフェースを実装すべきメソッドが二つしかないという状況なので、コンパイルエラーの原因はインタフェースが定義している Move() の実装し忘れのせいだと気づくことができる。しかし上記のメッセージでは、Cat がインタフェースのメソッドのMove() を実装し忘れてエラーになっているのか、Cat独自のメソッドとしてのMove() の実装し忘れているのか、どちらがエラーの原因なのか区別がつきにくい。インタフェースで定義されているメソッドがもっと多い場合や、インタフェースとそれを実装する型のパッケージが違うパスにある場合には、さらにエラー原因の特定が難しくなると思う。

もっと問題なのは、先ほどの例にもあったように、もし、未定義の c.Move() を呼び出す実際のコードがない場合(Cat の単体テストで Move() に対するテストを書いていない場合など)、エラーが発生しないということ。

対応策

そこで、[2] でも説明されているが、コンパイル時に CatがAnimalインタフェースのメソッドをすべて実装しているかどうかチェックするための方法(一種のhack)として、以下の行を Cat を定義する同じパッケージ(今回の場合は main)に追加する。

var _ Animal = (*Cat)(nil)

これを一般的 (?) に書くと、

var _ インタフェース = (*独自の型)(nil)

となる(イコールの右側は nil から「インタフェースを実装している型へのポインタ」への型変換することを意味する [3])。 上のテクニックを先ほどのコード例に適用したものを以下に示す。

package main

import "fmt"

type Animal interface {
    Bark()
    Move()
}

type Cat struct{}

// Cat implements the Animal interface.
// See: http://golang.org/doc/faq#guarantee_satisfies_interface
var _ Animal = (*Cat)(nil)

func (_ *Cat) Bark() { fmt.Printf("にゃーん\n") }

func main() {
    var c Cat
    c.Bark()
    // c.Move()
}

http://play.golang.org/p/sjs-5dXIAS

このコードを実行してみると分かるが、このテクニックを使うと、実際に Move() を呼ぶコードがなくても、インタフェースを実装すべきメソッドが不足しているという旨のコンパイルエラーが表示される:

prog.go:14: cannot use (*Cat)(nil) (type *Cat) as type Animal in assignment:
    *Cat does not implement Animal (missing Move method)

詳しい説明は以下のリンク先を参照のこと。

追記: コード例が一部間違っていたので、修正。