【Go】var _ T の使い道
はじめに
GoでProtocolBufferのコードを自動生成した時にvar _ context.Context
という書き方を見つけて「なんじゃこりゃ」と思ったので、こう書くことで何が嬉しいのか調べてみました。
var _ T
の効果
var _ T
は、ブランク識別子(Blank Identifier)と呼ばれている_
を使った空の宣言です。宣言しても変数を使うことはできません。一見何のご利益があるのか分からない宣言なのですが、型部分のTだけに用がある場合に効果を発揮します。
import and not used
エラーを回避できる
Goはimportしたパッケージを一度も使用しなかったりするとコンパイル時にnot usedエラーを律儀に出力します。ただ、初期化処理用途でimportだけしたいケースもあるかと思います。その場合、importするパッケージの前に_
(アンダースコア)を入れることで、コードから参照されなくともコンパイルエラーされなくなります。
import (
_ "context"
)
上記のアプローチの別解として、var _ T
が使えます。以下の例では、contextを通常通りimportして、var _ context.Context
と宣言しています。この時に、コードからcontextパッケージを読んでいる状態になるのでコンパイルエラーが発生しなくなります。
import (
"context"
)
var _ context.Context
ProtocolBufferの自動生成コードを改めて読んでみると、以下のコメントがついていました。ここでも未使用時のエラーを回避する用途で書かれているようです。
...
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
...
interfaceの実装を明示的に宣言する
Goはダックタイピング(Duck Typing)を採用しており、structがどのinterfaceを実装しているのか宣言しなくても、interfaceの振る舞いを実装していれば暗黙的にそのinterfaceで扱えます。この仕様によって、interfaceをゆるふわに扱えるようになってます。
一方でそのstructが一体何のinterfaceを実装しているのかが分からないこともあるかと思います。その場合、var _ T
を使うと実装しているinterfaceを明示することができます。
以下はWorkerというinterfaceをManが実装しているコードです。
var _ Worker = Man{}
でManをWorkerにアサーションできるかだけ検証します。
このコードだとコンパイルが通り、何もせず終了します。
type Worker interface {
Do()
}
type Man struct{}
func (m Man) Do() {
fmt.Println("done")
}
var _ Worker = Man{} // interfaceを実装していることを明示
func main() {}
ここで、ManがWorkerのinterfaceを実装していない状態にすると、コンパイル時に、var _ Worker = Man{}
の行でnot implementエラーが出るようになります。
- func (m Man) Do() {
- fmt.Println("done")
- }
コンパイルエラー
./prog.go:19:16: cannot use Man{} (value of type Man) as Worker value in variable declaration: Man does not implement Worker (missing method Do)
interfaceとstructでパッケージが分かれているような実装をしている時に、このstructが一体何のinterfaceを実装しているのか明示したい場合は、このTipsが使えるかもしれません。
Discussion