🦆

【Go】var _ T の使い道

2023/09/14に公開

はじめに

GoでProtocolBufferのコードを自動生成した時にvar _ context.Contextという書き方を見つけて「なんじゃこりゃ」と思ったので、こう書くことで何が嬉しいのか調べてみました。

var _ Tの効果

var _ Tは、ブランク識別子(Blank Identifier)と呼ばれている_を使った空の宣言です。宣言しても変数を使うことはできません。一見何のご利益があるのか分からない宣言なのですが、型部分のTだけに用がある場合に効果を発揮します。

import and not usedエラーを回避できる

Goはimportしたパッケージを一度も使用しなかったりするとコンパイル時にnot usedエラーを律儀に出力します。ただ、初期化処理用途でimportだけしたいケースもあるかと思います。その場合、importするパッケージの前に_(アンダースコア)を入れることで、コードから参照されなくともコンパイルエラーされなくなります。

import (
	_ "context"
)

go playground

上記のアプローチの別解として、var _ Tが使えます。以下の例では、contextを通常通りimportして、var _ context.Contextと宣言しています。この時に、コードからcontextパッケージを読んでいる状態になるのでコンパイルエラーが発生しなくなります。

import (
	"context"
)
var _ context.Context

go playground

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() {}

go playground

ここで、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)

go playground

interfaceとstructでパッケージが分かれているような実装をしている時に、このstructが一体何のinterfaceを実装しているのか明示したい場合は、このTipsが使えるかもしれません。

Discussion