[インデックス 19441] ファイルの概要
このコミットは、Go言語の仕様書において、インターフェース型にブランク識別子(_
)を用いたメソッドを定義することを明示的に禁止する変更を加えています。これにより、Goコンパイラやツール間での動作の不整合を解消し、仕様の曖昧さを排除することを目的としています。
コミット
- コミットハッシュ:
2c83f1eaf93c3d1891588e82b4fd4e761d161fdd
- 作者: Robert Griesemer gri@golang.org
- 日付: 2014年5月22日 木曜日 12:23:25 -0700
- コミットメッセージ:
spec: explicitly disallow blank methods in interface types
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2c83f1eaf93c3d1891588e82b4fd4e761d161fdd
元コミット内容
spec: explicitly disallow blank methods in interface types
The spec was unclear about whether blank methods should be
permitted in interface types. gccgo permits at most one, gc
crashes if there are more than one, go/types permits at most
one.
Discussion:
Since method sets of non-interface types never contain methods
with blank names (blank methods are never declared), it is impossible
to satisfy an interface with a blank method.
It is possible to declare variables of assignable interface types
(but not necessarily identical types) containing blank methods, and
assign those variables to each other, but the values of those
variables can only be nil.
There appear to be two "reasonable" alternatives:
1) Permit at most one blank method (since method names must be unique),
and consider it part of the interface. This is what appears to happen
now, with corner-case bugs. Such interfaces can never be implemented.
2) Permit arbitrary many blank methods but ignore them. This appears
to be closer to the handling of blank identifiers in declarations.
However, an interface type literal is not a declaration (it's a type
literal). Also, for struct types, blank identifiers are not ignored;
so the analogy with declarations is flawed.
Both these alternatives don't seem to add any benefit and are likely
(if only slightly) more complicated to explain and implement than
disallowing blank methods in interfaces altogether.
Fixes #6604.
LGTM=r, rsc, iant
R=r, rsc, ken, iant
CC=golang-codereviews
https://golang.org/cl/99410046
変更の背景
このコミットが行われた背景には、Go言語のインターフェース型における「ブランクメソッド」(メソッド名にブランク識別子 _
を使用したメソッド)の扱いに関する仕様の曖昧さと、それによって引き起こされるGoコンパイラやツール間での動作の不整合がありました。
具体的には、以下のような問題が指摘されていました。
- 仕様の不明確さ: Go言語の仕様書では、インターフェース型にブランクメソッドを定義することが許されるかどうかについて明確な記述がありませんでした。
- ツール間の不整合:
gccgo
(GCCベースのGoコンパイラ) は、インターフェース型に最大1つのブランクメソッドを許可していました。gc
(公式のGoコンパイラ) は、複数のブランクメソッドが存在するとクラッシュする可能性がありました。go/types
(Goの型チェッカーライブラリ) も、最大1つのブランクメソッドを許可していました。 このように、異なるGoツール間でブランクメソッドの解釈や挙動が異なり、開発者にとって混乱の原因となっていました。
- ブランクメソッドを持つインターフェースの実装不可能性: Goのインターフェースは、そのインターフェースが定義するすべてのメソッドを実装する型によって満たされます。しかし、非インターフェース型のメソッドセットには、ブランク識別子を持つメソッド(つまり、名前が
_
のメソッド)は含まれません。これは、メソッドが宣言される際に常に具体的な名前を持つためです。したがって、ブランクメソッドを持つインターフェースは、いかなる具象型によっても満たされることが原理的に不可能でした。 - 実用性の欠如: ブランクメソッドを持つインターフェース型の変数を宣言し、それらを互いに代入することは可能でしたが、それらの変数の値は常に
nil
にしかなりませんでした。これは、そのようなインターフェースが実用的な目的を持たないことを示しています。
これらの問題を踏まえ、Go言語の設計者たちは、ブランクメソッドをインターフェース型で完全に禁止することが、仕様を明確にし、ツール間の一貫性を保ち、かつ言語の複雑さを不必要に増大させない最も合理的な解決策であると判断しました。
前提知識の解説
Goのインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースの最大の特徴は、その「暗黙的な実装」にあります。これは、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型は自動的にそのインターフェースを満たすと見なされるというものです。implements
キーワードのような明示的な宣言は不要です。
インターフェースは、ポリモーフィズムを実現し、疎結合なコードを書くための強力なメカニズムを提供します。例えば、io.Reader
インターフェースは Read
メソッドを持つことを定義し、ファイル、ネットワーク接続、メモリバッファなど、様々なデータソースが io.Reader
として扱われることを可能にします。
各型には「メソッドセット」が関連付けられています。インターフェース型のメソッドセットは、そのインターフェース自体です。非インターフェース型(構造体、プリミティブ型など)のメソッドセットは、その型にレシーバとして宣言されたすべてのメソッドの集合です。ポインタ型のメソッドセットは、そのポインタ型にレシーバとして宣言されたメソッドと、対応する非ポインタ型にレシーバとして宣言されたメソッドの両方を含みます。
ブランク識別子 (_
)
Go言語におけるブランク識別子(_
)は、値を意図的に無視するための特別なプレースホルダーです。Goのコンパイラは、宣言された変数やインポートされたパッケージが使用されていない場合にエラーを発生させますが、ブランク識別子を使用することで、このエラーを回避し、不要な値を破棄することができます。
ブランク識別子の一般的な用途は以下の通りです。
- 関数の戻り値の無視: 複数の戻り値を持つ関数で、一部の戻り値が不要な場合に
_
を使用して無視します。_, err := strconv.Atoi("123") // 変換された整数値は不要で、エラーだけをチェックしたい場合
- パッケージの副作用のためのインポート: パッケージの初期化関数(
init()
)の副作用だけを利用したいが、そのパッケージ内のエクスポートされた識別子を直接使用しない場合にimport _ "package/path"
と記述します。import _ "net/http/pprof" // pprofのHTTPエンドポイントを登録するためだけにインポート
- ループ変数の無視:
for...range
ループで、インデックスまたは値のいずれか一方が不要な場合に_
を使用します。for _, value := range slice { // インデックスは不要で、値だけを使いたい場合 fmt.Println(value) }
- 未使用変数の抑制: 開発中に一時的に未使用となる変数のコンパイラエラーを抑制するために使用することもあります。
メソッドとブランク識別子
ブランク識別子は、メソッドの定義においても関連する場合があります。
- メソッドの引数の無視: インターフェースを実装する際に、メソッドのシグネチャがインターフェースと一致している必要がありますが、実装内で特定の引数が使用されない場合があります。このとき、その引数名を
_
にすることで、未使用引数に関するコンパイラエラーを回避できます。type MyInterface interface { DoSomething(param1 string, param2 int) } type MyType struct{} func (m MyType) DoSomething(param1 string, _ int) { // param2は使用しない fmt.Println("Doing something with:", param1) }
- コンパイル時インターフェースチェック: ある型が特定のインターフェースを実装していることをコンパイル時に確認するために、ブランク識別子を使用するイディオムがあります。
これは、var _ io.Writer = (*MyWriter)(nil) // MyWriterがio.Writerを実装しているかチェック
MyWriter
型がio.Writer
インターフェースの契約を満たしていることを保証し、もし実装が変更されてインターフェースを満たさなくなった場合にコンパイルエラーを発生させます。
しかし、このコミットで問題となっているのは、上記のような「メソッドの引数」や「コンパイル時チェック」でのブランク識別子の使用ではなく、メソッド名自体にブランク識別子を使用すること、つまり func (r Receiver) _() {}
のような形式のメソッドをインターフェースに含めることでした。
技術的詳細
このコミットの技術的な核心は、Go言語の型システムとインターフェースの設計原則に深く根ざしています。
Goのインターフェースは、そのメソッドセットによって定義されます。ある型がインターフェースを「実装する」とは、その型のメソッドセットがインターフェースのメソッドセットのスーパーセットである、つまりインターフェースが要求するすべてのメソッドをその型が持っていることを意味します。
問題は、Goのメソッド宣言において、メソッド名は常に具体的な識別子でなければならないという点にあります。ブランク識別子 _
は、変数やインポートパスなど、特定の文脈で「値を無視する」ために使用されますが、メソッド名として使用されることはありません。したがって、具象型が _
という名前のメソッドを持つことはありません。
このため、もしインターフェースが _
という名前のメソッドを要求した場合、いかなる具象型もそのインターフェースを満たすことができません。なぜなら、具象型は _
という名前のメソッドを宣言できないからです。これは、インターフェースの本来の目的(異なる具象型が共通の振る舞いを共有するための契約を定義すること)に反します。
コミットメッセージでは、この状況についてさらに詳しく説明されています。
- 「非インターフェース型のメソッドセットには、ブランク名のメソッドは決して含まれない(ブランクメソッドは決して宣言されない)ため、ブランクメソッドを持つインターフェースを満たすことは不可能である。」 これは、ブランクメソッドを持つインターフェースが本質的に「実装不可能なインターフェース」であることを明確にしています。
- 「ブランクメソッドを含む代入可能なインターフェース型(ただし、必ずしも同一の型ではない)の変数を宣言し、それらの変数を互いに代入することは可能であるが、それらの変数の値は
nil
にしかならない。」 これは、そのようなインターフェース型が、たとえ構文的に許容されたとしても、実用的な意味を持たないことを示しています。nil
以外の値を保持できないインターフェースは、何の役にも立ちません。
コミットの議論では、この問題に対する2つの代替案が検討されましたが、いずれも却下されました。
- 最大1つのブランクメソッドを許可し、それをインターフェースの一部と見なす: これは、メソッド名が一意でなければならないというルールに基づいています。しかし、このアプローチは「コーナーケースのバグ」を引き起こす可能性があり、何よりも「そのようなインターフェースは決して実装できない」という根本的な問題が残ります。
- 任意の数のブランクメソッドを許可し、それらを無視する: これは、宣言におけるブランク識別子の扱いに近いように見えます。しかし、インターフェース型リテラルは宣言ではなく型リテラルであり、構造体型ではブランク識別子が無視されない(匿名フィールドとして扱われる)ため、この類推は不適切であると判断されました。
最終的に、これらの代替案はどちらもメリットがなく、説明や実装を不必要に複雑にするだけであると結論付けられました。最もシンプルで一貫性のある解決策は、インターフェースにおけるブランクメソッドを完全に禁止することでした。これにより、Go言語の仕様がより明確になり、コンパイラやツール間の動作の不整合が解消され、言語の健全性が保たれます。
この変更は、Go言語の設計哲学である「シンプルさ」と「明確さ」を反映したものです。曖昧な挙動や実用性のない機能は排除し、開発者が直感的に理解し、一貫した結果を得られるようにするという原則に基づいています。
コアとなるコードの変更箇所
このコミットによるコアとなるコードの変更は、Go言語の公式仕様書である doc/go_spec.html
ファイルに対して行われています。具体的には、メソッドセットとインターフェース型に関する記述が修正され、ブランク識別子をメソッド名として使用することが明示的に禁止されました。
変更された箇所は以下の通りです。
-
doc/go_spec.html
のMethod_sets
セクション: メソッドセットにおけるメソッド名のユニーク性に関する記述が変更されました。--- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -696,19 +696,19 @@ and <code>T4</code> is <code>[]T1</code>. <h3 id="Method_sets">Method sets</h3> <p> -A type may have a <i>method set</i> associated with it -(§<a href="#Interface_types">Interface types</a>, §<a href="#Method_declarations">Method declarations</a>). +A type may have a <i>method set</i> associated with it. The method set of an <a href="#Interface_types">interface type</a> is its interface. -The method set of any other type <code>T</code> -consists of all methods with receiver type <code>T</code>. -The method set of the corresponding pointer type <code>*T</code> -is the set of all methods with receiver <code>*T</code> or <code>T</code> +The method set of any other type <code>T</code> consists of all +<a href="#Method_declarations">methods</a> declared with receiver type <code>T</code>. +The method set of the corresponding <a href="#Pointer_types">pointer type</a> <code>*T</code> +is the set of all methods declared with receiver <code>*T</code> or <code>T</code> (that is, it also contains the method set of <code>T</code>). Further rules apply to structs containing anonymous fields, as described in the section on <a href="#Struct_types">struct types</a>. Any other type has an empty method set. In a method set, each method must have a -<a href="#Uniqueness_of_identifiers">unique</a> <a href="#MethodName">method name</a>. +<a href="#Uniqueness_of_identifiers">unique</a> +non-<a href="#Blank_identifier">blank</a> <a href="#MethodName">method name</a>. </p>
-
doc/go_spec.html
のInterface_types
セクション(インターフェース型の定義に関する箇所): インターフェース型におけるメソッド名のユニーク性に関する記述が変更されました。--- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1109,7 +1109,8 @@ InterfaceTypeName = TypeName . <p> As with all method sets, in an interface type, each method must have a -<a href="#Uniqueness_of_identifiers">unique</a> name. +<a href="#Uniqueness_of_identifiers">unique</a> +non-<a href="#Blank_identifier">blank</a> name. </p>
これらの変更は、Go言語の仕様書に直接手を加えることで、インターフェースにおけるブランクメソッドの扱いを明確にし、将来的な混乱や不整合を防ぐためのものです。
コアとなるコードの解説
変更された doc/go_spec.html
の各行について詳しく解説します。
1. Method_sets
セクションの変更
このセクションは、Goの型が持つメソッドセットの定義について説明しています。変更前は、メソッドセット内の各メソッドは「ユニークなメソッド名」を持つ必要があるとされていました。
- <a href="#Uniqueness_of_identifiers">unique</a> <a href="#MethodName">method name</a>.
+ <a href="#Uniqueness_of_identifiers">unique</a>
+ non-<a href="#Blank_identifier">blank</a> <a href="#MethodName">method name</a>.
この変更により、「ユニークなメソッド名」という記述に加えて、新たに「非ブランクなメソッド名」という制約が追加されました。
non-<a href="#Blank_identifier">blank</a>
: これは、メソッド名がブランク識別子(_
)であってはならないことを明示的に示しています。<a href="#Blank_identifier">Blank_identifier</a>
: これは、仕様書内のブランク識別子に関するセクションへのリンクです。これにより、読者はブランク識別子の定義と用途をすぐに参照できます。
この修正は、メソッドセット全般(インターフェース型だけでなく、具象型も含む)に適用されるルールとして、メソッド名が _
であってはならないことを明確にしています。これにより、Goの型システム全体で、メソッド名には有効な非ブランク識別子が必要であることが強調されます。
2. Interface_types
セクションの変更
このセクションは、インターフェース型の定義と特性について説明しています。インターフェース型内のメソッドのユニーク性に関する記述が変更されました。
- <a href="#Uniqueness_of_identifiers">unique</a> name.
+ <a href="#Uniqueness_of_identifiers">unique</a>
+ non-<a href="#Blank_identifier">blank</a> name.
ここでも同様に、「ユニークな名前」という記述に「非ブランクな名前」という制約が追加されました。
non-<a href="#Blank_identifier">blank</a>
: インターフェース型内で定義されるメソッドの名前がブランク識別子であってはならないことを明確にしています。
この変更は、インターフェース型に特化したルールとして、ブランクメソッドの定義を禁止するものです。これにより、インターフェースの定義がより厳密になり、前述したような実装不可能性やツール間の不整合の問題が、仕様レベルで解決されます。
これらの変更は、Go言語の仕様をより堅牢で明確なものにし、開発者がインターフェースを扱う際の混乱を避けるための重要なステップです。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/2c83f1eaf93c3d1891588e82b4fd4e761d161fdd
- Gerrit Code Review: https://golang.org/cl/99410046 (このコミットの元のコードレビューページ)
- Go Issue #6604: このコミットが修正したとされるGoのIssue番号。直接的なGitHubのIssueページは見つかりませんでしたが、コミットメッセージに記載されています。
参考にした情報源リンク
- Go言語のブランク識別子に関する一般的な情報:
- https://yourbasic.org/go/blank-identifier/
- https://rezmoss.com/go-blank-identifier/
- https://www.educative.io/answers/what-is-the-blank-identifier-in-go
- https://www.geeksforgeeks.org/blank-identifier-in-go/
- https://medium.com/@prashant.sharma_75672/blank-identifier-in-go-a-comprehensive-guide-221212121212
- Go言語のインターフェースに関する一般的な情報:
- https://medium.com/@prashant.sharma_75672/go-interfaces-a-comprehensive-guide-221212121212
- https://lumochift.org/go-interface-implementation-check/
- https://go.dev/doc/effective_go#interfaces (Effective Go - Interfaces)
- https://go.dev/ref/spec#Method_sets (Go Language Specification - Method sets)
- https://go.dev/ref/spec#Interface_types (Go Language Specification - Interface types)