はじめに
Goを書いていると、以下のようなコードを見かけることがあります。
1 | var _ Interface = (*Type)(nil) |
この一見奇妙なコードは、Goのインターフェース実装をコンパイル時にチェックするためのイディオムです。今回は、このイディオムの目的、必要性、そして適切な使用場面について詳しく解説します。
このイディオムの目的
基本的な仕組み
もう少し具体的なコードで説明してみます。以下のコードは、Counterインターフェースを実装していることをチェックするイディオムです。
1 | var _ Counter = (*CountRepository)(nil) |
このコードは以下の要素で構成されています:
var _
: 空白識別子(blank identifier)を使用した変数宣言Counter
: チェック対象のインターフェース(*CountRepository)(nil)
: チェック対象の型(nilポインタ)
何をしているのか
このイディオムは、*CountRepository
型が Counter
インターフェースを実装しているかをコンパイル時にチェックします。
実際の動作例
正常な場合
以下のコードは Counter
インターフェースを実装したサンプルコードです。
1 | package main |
CountRepositoryはCounterインターフェースで定義してある、Inctement、Get、Resetが実装してあるため、ビルドコマンドでビルドすることができます。
1 | go build sample.go |
エラーの場合
エラーを発生させるために、Counter
インターフェースにDecrementを追加してみましょう。
1 | type Counter interface { |
ビルドすると、ビルドエラーが発生します。インターフェースの未実装が検知されていますね。
コンパイルエラーメッセージ:
1 | ./sample.go:35:17: cannot use (*CountRepository)(nil) (value of type *CountRepository) as Counter value in variable declaration: *CountRepository does not implement Counter (missing method Decrement) |
では、イディオムをコメントアウトしてみるとどうでしょうか。
1 | // インターフェース実装チェック |
ビルドすると、エラーが発生しなくなりました。これは、コンパイル時にインターフェースの未実装が検知されていないことを意味しています。
なぜこのイディオムが必要なのか
このイディオムですが、通常は不要な場合がほとんどです。
例えば、以下のように NewCounter
関数を追加して、CountRepository
は Counter
インターフェースであるということを明示的に指定した場合、GOのコンパイラでインターフェースの実装を検知することができます。
1 | type CountRepository struct { |
エフェクティブGoの指針
エフェクティブGoでは、このイディオムについて以下のように述べています。
The appearance of the blank identifier in this construct indicates that the declaration exists only for the type checking, not to create a variable. Don’t do this for every type that satisfies an interface, though. By convention, such declarations are only used when there are no static conversions already present in the code, which is a rare event.
重要なポイント
- 空白識別子の役割 : 変数を作成せず、型チェックのみを目的とする
- 乱用すべきではない : すべてのインターフェース実装に使うべきではない
- 静的変換がない場合のみ : 既に型チェックの機会がある場合は不要
使いどころは、静的変換がない場面 です。
静的変換とは
静的変換(static conversion) とは、コンパイル時に型の互換性をチェックする機会のことです。
静的変換がある場合(イディオムは不要)
1 | func Process(repo Counter) { // ← 静的変換が発生 |
静的変換がない場合(イディオムが有用)
1 | // インターフェースと実装が分離されていて、 |
適切な使用場面
1. リポジトリパターン
1 | // domain/repository/user.go |
2. プラグインアーキテクチャ
1 | // plugin/interface.go |
不適切な使用例
既に静的変換がある場合
既に静的変換がある場合はイディオムは必要ありません。
1 | // 既に使用箇所がある場合 |
まとめ
var _ Interface = (*Type)(nil)
イディオムは 静的変換がない場合のみ 使用すればOKです。