🛡️

[Go小ネタ] type listを含むinterfaceのinterface guard

2022/09/21に公開

Goには型がインターフェイスを満たしていることを明示的に示すjsのimplementsのようなものがないため、代用としてしばしばinterface guardと呼ばれる記法が使われることがあります。

type Hoge interface{
  Hello()
}

// HogeImplはHogeインターフェイスを実装している
type HogeImpl struct{}

func (h *HogeImpl) Hello() {
  fmt.Println("Hello, 世界")
}

// interface guard
// 左辺はここ以外で使わないためアンダースコアにする
// 右辺の値は何でもよいがゼロ値を入れておくと記述が楽
var _ Hoge = (*HogeImpl)(nil)

interface guardを書いておくことで、例えば*HogeImpl型にHello()が実装されていない場合、以下のようなコンパイルエラーを出力してくれます。

cannot use (*HogeImpl)(nil) (value of type *HogeImpl) as type Hoge in variable declaration:
	*HogeImpl does not implement Hoge (missing Hello method)

しかし、Go1.18から追加されたジェネリクスが絡んでくると、こう簡単にはいかない場合があります。具体的にはインターフェイスが内部のプロパティにtype list(その型に代入可能な型のリスト)を持っていると不具合が生じます。

type Hoge interface {
  ~int // int型に代入可能な型のリスト
}

// HogeImplはint型に代入可能なのでHogeインターフェイスを実装している
type HogeImpl int

// interface guard
var _ Hoge = (HogeImpl)(0)

これは先ほど同様うまくいくように思われますが、実際は以下のコンパイルエラーが発生します。

cannot use type Hoge outside a type constraint: interface contains type constraints

この問題は、インターフェイスを実装している型をgeneric typeに含めた空の構造体(以下のHogeIsImplementedBy)を定義することで解決することができます。

type Hoge interface {
  ~int
}

type HogeIsImplementedBy[T Hoge] struct{}

type HogeImpl int

// interface guard
var _ HogeIsImplementedBy[HogeImpl]

これでコンパイルエラーを消すことができました!
さらに、HogeHogeImplによって実装されていることも命名から明らかです。

定義する型が1つ増えてしまうため少し冗長ではありますが、type listを含んだインターフェイスのinterface guardを書きたい場合には有効です。

また、将来的にはtype listを含めることも検討されているようです。

Discussion