[インデックス 12285] ファイルの概要
このコミットは、Go言語の仕様書(doc/go_spec.html
)におけるメソッドセットと埋め込みに関する記述を修正し、これらの概念が「名前付き型」だけでなく「すべての型」に適用されることを明確にするものです。特に、構造体への型の埋め込みが、明示的なメソッド宣言なしにメソッドを導入できるようになったことで、この仕様の明確化が必要となりました。
コミット
commit 8e38b17a906e7208fe9133cedd154758ae4f717d
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 29 15:54:06 2012 -0500
spec: apply method sets, embedding to all types, not just named types
When we first wrote the method set definition, we had long
discussions about whether method sets applied to all types
or just named types, and we (or at least I) concluded that it
didn't matter: the two were equivalent points of view, because
the only way to introduce a new method was to write a method
function, which requires a named receiver type.
However, the addition of embedded types changed this.
Embedding can introduce a method without writing an explicit
method function, as in:
var x struct {
sync.Mutex
}
var px *struct {
sync.Mutex
}
var _, _ sync.Locker = &x, px
The edits in this CL make clear that both &x and px satisfy
sync.Locker. Today, gccgo already works this way; 6g does not.
R=golang-dev, gri, iant, r
CC=golang-dev
https://golang.org/cl/5702062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8e38b17a906e7208fe9133cedd154758ae4f717d
元コミット内容
このコミットは、Go言語の仕様書(doc/go_spec.html
)を更新し、メソッドセットと型の埋め込みの適用範囲を「名前付き型」から「すべての型」へと拡張することを明確にしています。
元々のメソッドセットの定義では、新しいメソッドを導入するには名前付きレシーバ型を必要とするメソッド関数を記述する必要があったため、メソッドセットが「すべての型」に適用されるか「名前付き型」にのみ適用されるかは、実質的に同じ意味を持つと考えられていました。
しかし、Go言語に「埋め込み型」の機能が追加されたことで、この前提が崩れました。埋め込み型を使用すると、明示的なメソッド関数を記述することなく、既存の型のメソッドを新しい型に「昇格」させることができます。この変更により、匿名構造体のような名前を持たない型でもメソッドを持つことが可能になり、それらの型がインターフェースを満たすかどうかの判断基準が曖昧になりました。
このコミットは、sync.Mutex
を埋め込んだ匿名構造体の例を挙げ、その匿名構造体(およびそのポインタ)がsync.Locker
インターフェースを満たすべきであることを明確にしています。コミット時点では、gccgo
コンパイラはこの挙動を既にサポートしていましたが、当時の主要なGoコンパイラである6g
はサポートしていませんでした。この仕様変更は、コンパイラ間の挙動の統一と、Go言語のセマンティクスの一貫性を確保することを目的としています。
変更の背景
Go言語の初期設計段階では、メソッドセットの概念が「名前付き型」にのみ適用されるのか、それとも「すべての型」に適用されるのかについて議論がありました。当時の結論としては、新しいメソッドを定義するには必ず名前付きのレシーバ型が必要であったため、どちらの視点も実質的に同じ結果をもたらすというものでした。つまり、名前を持たない型が独自のメソッドを持つことは想定されていなかったため、この区別は重要ではないとされていました。
しかし、Go言語に「型の埋め込み(embedding)」という強力な機能が導入されたことで、この状況は一変しました。型の埋め込みは、ある構造体の中に別の型を匿名フィールドとして含めることで、その埋め込まれた型のフィールドやメソッドを外側の構造体のフィールドやメソッドとして「昇格」させるメカニズムです。この機能により、明示的にメソッドを宣言することなく、匿名構造体のような名前を持たない型でも、埋め込まれた型が持つメソッドを「継承」する形で利用できるようになりました。
この新しい機能が導入された結果、例えばstruct { sync.Mutex }
のような匿名構造体がsync.Mutex
のメソッド(Lock
, Unlock
など)を持つことになり、それらのメソッドを通じてsync.Locker
インターフェースを満たすべきかどうかが問題となりました。当時の6g
コンパイラはこれを認識せず、gccgo
コンパイラは既に認識しているという実装の不一致も存在しました。
このコミットは、このような状況を受けて、Go言語の仕様を明確にし、メソッドセットと埋め込みのルールが名前の有無にかかわらず「すべての型」に適用されることを明文化することで、言語のセマンティクスの一貫性を保ち、コンパイラの実装間の差異を解消することを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念について理解しておく必要があります。
-
型 (Types)
- 名前付き型 (Named Types):
type MyInt int
のようにtype
キーワードを使って宣言され、名前を持つ型です。int
,string
,bool
などの組み込み型も名前付き型です。 - 匿名型 (Unnamed Types) / 型リテラル (Type Literals):
struct { Field int }
,[]int
,map[string]string
,func()
,chan int
のように、type
キーワードを使わずに直接記述される型です。これらは名前を持ちませんが、有効な型です。
- 名前付き型 (Named Types):
-
メソッド (Methods)
- Go言語のメソッドは、特定の型に関連付けられた関数です。メソッドはレシーバ引数(receiver argument)を持ち、これによりどの型に属するメソッドであるかが示されます。
- 例:
func (t MyType) MyMethod() {}
ここでMyType
がレシーバ型です。
-
メソッドセット (Method Sets)
- 各型は、その型が持つメソッドの集合である「メソッドセット」を持っています。
- 値型
T
のメソッドセット: レシーバがT
であるすべてのメソッドを含みます。 - ポインタ型
*T
のメソッドセット: レシーバが*T
であるすべてのメソッドと、レシーバがT
であるすべてのメソッドを含みます。これは、ポインタを通じて値のメソッドを呼び出すことができるためです。 - インターフェース型は、そのインターフェースが要求するメソッドの集合をメソッドセットとして持ちます。
-
インターフェース (Interfaces)
- インターフェースは、メソッドのシグネチャの集合を定義する型です。
- ある型がインターフェースのすべてのメソッドを実装している場合、その型はそのインターフェースを「満たす(satisfy)」と言われます。このとき、その型の値はインターフェース型の変数に代入できます。
- インターフェースを満たすかどうかは、型のメソッドセットによって決定されます。
-
型の埋め込み (Type Embedding)
- Go言語の構造体は、匿名フィールドとして別の型を埋め込むことができます。
- 埋め込まれた型のフィールドやメソッドは、外側の構造体のフィールドやメソッドとして「昇格(promoted)」されます。これにより、外側の構造体から直接、埋め込まれた型のフィールドやメソッドにアクセスできるようになります。
- 例:
type MyStruct struct { io.Reader // io.Readerを埋め込み // MyStructのインスタンスからReaderのReadメソッドに直接アクセスできる }
- この機能は、継承に似たコードの再利用メカニズムを提供しますが、Goの哲学である「コンポジション(合成)」に基づいています。
-
sync.Mutex
とsync.Locker
sync.Mutex
: Goの標準ライブラリsync
パッケージに含まれるミューテックス(相互排他ロック)の実装です。Lock()
とUnlock()
メソッドを持ちます。sync.Locker
インターフェース:Lock()
とUnlock()
という2つのメソッドを持つインターフェースです。sync.Mutex
はこのインターフェースを満たします。
-
Goコンパイラ (
6g
,gccgo
)6g
: コミット当時のGo言語の公式コンパイラ(gcツールチェーンの一部)。Go言語で書かれており、Goのソースコードをネイティブバイナリにコンパイルします。gccgo
: GCC(GNU Compiler Collection)のGo言語フロントエンドです。GCCのバックエンドを利用してGoのソースコードをコンパイルします。6g
とは異なる実装を持つため、挙動に差異が生じることがありました。
これらの概念を理解することで、なぜこのコミットが重要であり、Go言語のセマンティクスにどのような影響を与えるのかを深く把握することができます。
技術的詳細
このコミットの技術的詳細の核心は、Go言語の型システムにおける「メソッドセット」と「型の埋め込み」の相互作用、特に「匿名型」が関与する場合の挙動の明確化にあります。
Go言語の仕様では、各型が持つメソッドの集合である「メソッドセット」が定義されています。このメソッドセットは、その型が特定のインターフェースを満たすかどうかを判断する上で極めて重要です。コミット以前の仕様の解釈では、メソッドセットは主に「名前付き型」に適用されるものと見なされていました。これは、メソッドを宣言する際には、func (r ReceiverType) MethodName() {}
のように、レシーバ型が明示的な「名前付き型」である必要があったためです。したがって、名前を持たない匿名型が独自のメソッドを持つことは、直接的には不可能であると考えられていました。
しかし、「型の埋め込み」機能が導入されたことで、この前提が崩れました。型の埋め込みは、構造体の中に別の型を匿名フィールドとして含めることで、埋め込まれた型のメソッドを外側の構造体に「昇格」させるメカニズムです。この昇格されたメソッドは、外側の構造体から直接呼び出すことができます。
問題は、この「外側の構造体」が匿名型である場合に発生しました。例えば、コミットメッセージで示されている以下のコードスニペットを考えます。
var x struct {
sync.Mutex
}
var px *struct {
sync.Mutex
}
var _, _ sync.Locker = &x, px
ここで、struct { sync.Mutex }
は名前を持たない匿名構造体です。この匿名構造体はsync.Mutex
を埋め込んでいます。sync.Mutex
はLock()
とUnlock()
メソッドを持っており、これらはsync.Locker
インターフェースを構成します。型の埋め込みのルールにより、sync.Mutex
のLock()
とUnlock()
メソッドは匿名構造体struct { sync.Mutex }
に昇格されます。
このコミット以前の曖昧な仕様では、この匿名構造体struct { sync.Mutex }
がsync.Locker
インターフェースを満たすかどうかは明確ではありませんでした。もしメソッドセットが「名前付き型」にのみ適用されると厳密に解釈されるならば、名前を持たないstruct { sync.Mutex }
はインターフェースを満たさないことになります。しかし、直感的には、sync.Mutex
の機能が埋め込みによって利用可能になっている以上、この匿名構造体もsync.Locker
として振る舞えるべきです。
このコミットは、doc/go_spec.html
の記述を修正することで、この曖昧さを解消します。具体的には、メソッドセットの定義から「名前付き(named)」という限定を削除し、メソッドセットが「すべての型」に適用されることを明示します。これにより、匿名型であっても、埋め込みによって昇格されたメソッドを含めて、そのメソッドセットに基づいてインターフェースを満たすことができるようになります。
コミットメッセージにある「gccgo
already works this way; 6g
does not」という記述は、当時のGoコンパイラの実装がこの仕様の解釈において異なっていたことを示しています。gccgo
は既にこの直感的な挙動をサポートしていましたが、公式コンパイラである6g
はそうではありませんでした。この仕様の明確化は、6g
の挙動をgccgo
に合わせ、Go言語のセマンティクスの一貫性を保証するための基盤となります。
この変更は、Go言語の型システムにおける多態性(polymorphism)の適用範囲を広げ、より柔軟なコード設計を可能にする上で重要な意味を持ちます。特に、匿名構造体やその他の匿名型をインターフェースの要件を満たす型として利用できるようになることで、より簡潔で表現力豊かなコードを書く道が開かれました。
コアとなるコードの変更箇所
このコミットによるコードの変更は、Go言語の仕様書であるdoc/go_spec.html
の2箇所のみです。
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -678,7 +678,7 @@
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>).
The method set of an <a href="#Interface_types">interface type</a> is its interface.
-The method set of any other named type <code>T</code>
+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>
@@ -954,7 +954,7 @@
<p>
Fields and methods (§<a href="#Method_declarations">Method declarations</a>) of an anonymous field are
promoted to be ordinary fields and methods of the struct (§<a href="#Selectors">Selectors</a>).
-The following rules apply for a struct type named <code>S</code> and
+The following rules apply for a struct type <code>S</code> and
a type named <code>T</code>:
</p>
<ul>
変更点:
-
doc/go_spec.html
の 678行目付近:- 変更前:
The method set of any other named type <code>T</code>
- 変更後:
The method set of any other type <code>T</code>
- これは、メソッドセットの定義において、「名前付き型(named type)」という限定を削除し、「任意の型(any other type)」に適用されることを明確にしています。
- 変更前:
-
doc/go_spec.html
の 954行目付近:- 変更前:
The following rules apply for a struct type named <code>S</code> and
- 変更後:
The following rules apply for a struct type <code>S</code> and
- これは、構造体におけるフィールドとメソッドの昇格に関するルールが、「名前付き構造体型(named struct type)」だけでなく、「任意の構造体型(struct type)」に適用されることを明確にしています。
- 変更前:
これらの変更は、Go言語のセマンティクス自体を変更するものではなく、既存の挙動(特に型の埋め込みとインターフェースの関連)をより正確に反映するように仕様書の記述を修正するものです。これにより、コンパイラの実装が統一され、開発者が言語の挙動をより正確に理解できるようになります。
コアとなるコードの解説
このコミットにおけるコアとなるコードの変更は、Go言語の仕様書(doc/go_spec.html
)内のわずか2箇所のテキスト修正ですが、その意味するところはGoの型システムにおける重要な概念の明確化です。
1. メソッドセットの定義の変更
変更箇所:
- The method set of any other named type <code>T</code>
+ The method set of any other type <code>T</code>
この変更は、Go言語の「メソッドセット」の定義から「名前付き(named)」という限定詞を削除します。 Go言語では、各型がその型に属するメソッドの集合である「メソッドセット」を持っています。このメソッドセットは、その型が特定のインターフェースを満たすかどうかを判断する際の基準となります。
変更前は、「名前付き型Tのメソッドセットは、レシーバ型がTであるすべてのメソッドから構成される」と解釈される可能性がありました。これは、type MyType struct{}
のように明示的に名前が付けられた型にのみメソッドセットが適用されるという誤解を生む可能性がありました。
しかし、Goの「型の埋め込み」機能が導入されたことで、匿名型(例: struct { sync.Mutex }
)でも、埋め込まれた型のメソッドが昇格されることで、実質的にメソッドを持つことができるようになりました。この場合、匿名型は名前を持たないため、もしメソッドセットが名前付き型に限定されるとすれば、これらの匿名型がインターフェースを満たすことができなくなってしまいます。
この修正により、メソッドセットは「任意の型T」に適用されることが明確になります。これにより、匿名型であっても、埋め込みによって昇格されたメソッドを含めて、そのメソッドセットに基づいてインターフェースを満たすことができるようになります。これは、Goのインターフェースと埋め込みの設計意図に合致する、より柔軟で一貫性のある型システムを保証します。
2. 構造体におけるフィールドとメソッドの昇格ルールの変更
変更箇所:
- The following rules apply for a struct type named <code>S</code> and
+ The following rules apply for a struct type <code>S</code> and
この変更は、構造体における匿名フィールドのフィールドとメソッドの「昇格(promotion)」に関するルールが、「名前付き構造体型S」だけでなく、「任意の構造体型S」に適用されることを明確にします。
Go言語では、構造体に別の型を匿名フィールドとして埋め込むと、埋め込まれた型のフィールドやメソッドが外側の構造体のフィールドやメソッドとして「昇格」されます。これにより、外側の構造体のインスタンスから、埋め込まれた型のフィールドやメソッドに直接アクセスできるようになります。
変更前は、この昇格ルールが「名前付き構造体型S」にのみ適用されると解釈される可能性がありました。しかし、実際には匿名構造体(例: var x struct { Field int }
)でも型の埋め込みは可能であり、その場合もフィールドやメソッドは昇格されます。
この修正により、昇格ルールが名前の有無にかかわらず「任意の構造体型S」に適用されることが明確になります。これは、Goの型の埋め込み機能の普遍的な適用性を強調し、匿名構造体を含むすべての構造体で一貫した挙動が期待できることを示しています。
まとめ
これらの変更は、Go言語の仕様書における記述を、言語の実際の挙動と設計意図により正確に合致させるためのものです。特に、型の埋め込みによって匿名型がメソッドを持つようになった状況に対応し、それらの型がインターフェースを適切に満たせるように、メソッドセットの概念を拡張・明確化しています。これにより、Go言語の型システムの一貫性と予測可能性が向上し、開発者がより自信を持って言語の機能を活用できるようになります。
関連リンク
- Go言語の仕様書: https://go.dev/ref/spec
- Go言語の型システムに関する公式ドキュメントやブログ記事 (当時のもの):
- A Tour of Go - Methods: https://go.dev/tour/methods/1
- A Tour of Go - Interfaces: https://go.dev/tour/methods/9
- A Tour of Go - Embedded fields: https://go.dev/tour/methods/10
- Go言語のChange List (CL) 5702062: https://golang.org/cl/5702062 (このコミットの元となったCL)
参考にした情報源リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語の仕様書: https://go.dev/ref/spec
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語の歴史とコンパイラに関する情報 (一般的な知識として):
- The Go Programming Language (書籍)
- Go言語のブログ記事やカンファレンス発表 (当時のもの)
sync.Mutex
とsync.Locker
に関するGo標準ライブラリのドキュメント: https://pkg.go.dev/sync- Go言語における型の埋め込みとインターフェースに関する解説記事 (一般的なGoの学習リソース)