インタフェースは型名を公開しなくても実装できる #golang

August 29, 2016

この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。

インタフェースの実装

Goのインタフェースは、明示的に実装する必要がありません。 インタフェースが定義しているメソッドリストとその型が持つメソッドのリストが一致すれば実装していることになります。 インタフェースについて詳しく知りたい方は、以下の記事にまとめていますので参考にしてください。

同じメソッドリストをもつインタフェース

上記のような特徴からインタフェースは、typeで付けられた型名はさほど重要ではなく、どのメソッドを持っているかが重要になってきます。 以下のコードを見ると、Str型が3種類のインタフェースを実装していることがわかります。 それぞれ、外部のパッケージで定義された型、関数内で定義された型、型リテラルです。 名前は違いますが、どれも同じメソッドリストを持つインタフェースです。

関数内でtypeを使って型定義を行っていることに驚く方がいるかもしれませんが、関数内だけで有効な型を作りたい場合はこの方法が使えます。 また、型リテラルは型そのものを記述したもので、[]intなどもそれにあたります。 typeは型リテラルや名前の付いている既存の型に新たに名前をつけているだけだということに気づくと思います。

なお、あるインタフェース型の変数として_に代入する方法は、ある型が特定のインタフェースを実装しているかコンパイル時にチェックする方法としてイディオムになっています。 詳しくは、Effective Goを見ると良いと思います。

Go Playgroundで動かす

package main

import (
	"fmt"
)

type Str string

func (s Str) String() string {
	return string(s)
}

func main() {
	var s Str = "hoge"

	var _ fmt.Stringer = s

	type MyStringer interface {
		String() string
	}
	var _ MyStringer = s

	var _ interface {
		String() string
	} = s
}

インタフェースの型名を公開せずに利用する

pkg/errorsの実装を見ると、一見、見慣れないコードを見つけることができます。 Cause関数内で定義されているcauser型は、メソッド内で定義された型であるため、関数外(もちろん、外部パッケージも含む)から型名を知ることができません。 しかし、上述の通りインタフェースは、定義しているメソッドリストが重要なので型名を公開しなくても、そのインタフェースを実装しているかのチェックには使えます。 インタフェース型を引数に受け取ったり、戻り値として返す場合は、名前を付けて公開した方が便利なので、大文字で始まる名前をつけて関数外で定義するべきでしょう。 しかし、pkg/errorsパッケージでは、関数内でCauseメソッドを実装しているエラーかどうかを知りたいだけなので、インタフェース型リテラルに名前をつけて公開する必要がありません。

pkg/errorsから引用

func Cause(err error) error {
	type causer interface {
		Cause() error
	}

	for err != nil {
		cause, ok := err.(causer)
		if !ok {
			break
		}
		err = cause.Cause()
	}
	return err
}

なお、上記のコードは以下のようにも書けます。

func Cause(err error) error {
	for err != nil {
		cause, ok := err.(interface { Cause() error })
		if !ok {
			break
		}
		err = cause.Cause()
	}
	return err
}

まとめ

一見、pkg/errorsパッケージのコードは不思議ですが、よくよく考えると理にかなっています。 構造体や他の型の型だと外部パッケージに公開するには、大文字で始まる型名をつけて定義する必要がありますが、インタフェースはその制約はありません。 こういう使い方ができるGoのインタフェースは面白いですね。