It Made My Day

Acutally, my whole life is just one big yak shaving exercise.

Go:型アサーションでインターフェースへの準拠を確認する

最近久しぶりにこういう↓イディオムを見かけ、色々文脈があるなと感じたのでちゃんと調べてみた。

type A struct {}

var _ SampleType = (*A)(nil)

uber-goのスタイルガイドの「Verify Interface Compliance」よりソースコードを抜粋し、説明をまとめる。

github.com

type Handler struct {
  // ...
}

var _ http.Handler = (*Handler)(nil)  // ←これ

func (h *Handler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  // ...
}

*Handler が http.Handler インターフェースと一致しなくなると、var _ http.Handler = (*Handler)(nil)コンパイルに失敗する。

代入の右側 (*Handler)(nil) は、アサートされた型のゼロ値。これは、ポインター型 (*Handler など)、スライス、マップの場合は nil であり、構造体型の場合は空の構造体 (例:var _ http.Handler = LogHandler{})。そのため、メモリはアロケートされない。

この記述を追加することを議論したuber-goのイシューによると、このイディオムは、Javaimplements , Rustの impl ... for に相当する。APIの契約の一部として特定のインターフェースを満たす必要がある公開型がある場合、型が予期されるインターフェイスのセットを実装していることをアサートし、メソッド シグネチャの変更による破損を防ぐ。 Newから始まる名前を持つコンストラクタがインターフェースを返す場合はこの1行は必要ないが、特に外部へインターフェースを公開するパッケージではインターフェースの実装を変更することでユーザーのコードを壊さないために、構造体を返すことが良しとされていることから、インターフェースへの準拠をこの1行で担保する。また、満たすべき条件を明示し、ドキュメントの追加形式として機能させることでコードが読みやすくなるとのこと。

まとめると、この var _ SampleType = (*A)(nil) イディオムは具象型がインターフェースを満たしていることをコンパイル時に保証するためのもの。Goのインターフェースは暗黙に満たされるものであるため、インターフェースを実装する側がインターフェースへの準拠を保証するために使う、ということだと思っている。
アプリケーション開発時のこのコードのニーズについてmattnさんが説明してくれていた。