[インデックス 15853] ファイルの概要
このコミットは、Go言語の仕様書(doc/go_spec.html
)に「メソッド値(Method values)」の定義を追加するものです。これにより、Go言語におけるメソッドの扱いがより明確になり、特定のレシーバにバインドされた関数を生成する機能が正式に仕様として定義されました。
コミット
commit 6e15683caee9085dfc953c39a49f13ccc9dc1c86
Author: Russ Cox <rsc@golang.org>
Date: Wed Mar 20 16:54:07 2013 -0400
spec: define method values
Fixes #2280.
R=golang-dev, r, bradfitz, iant, andybalholm, gri
CC=golang-dev
https://golang.org/cl/7816045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6e15683caee9085dfc953c39a49f13ccc9dc1c86
元コミット内容
このコミットの元々の内容は、Go言語の仕様書にメソッド値に関する新しいセクションを追加し、既存の関連記述を修正することです。具体的には、doc/go_spec.html
ファイルが変更され、メソッド値の概念、その型、および使用例が詳細に記述されています。また、nil
ポインタやインターフェースの扱いに関する既存の記述も、メソッド値の文脈に合わせて更新されています。
変更の背景
この変更の背景には、Go言語における「メソッド値」の振る舞いを明確に定義し、言語仕様として文書化する必要性がありました。Go言語では、構造体やインターフェースのメソッドを関数として扱うことができます。例えば、t.Mv
のように記述することで、t
という特定のレシーバにバインドされたMv
メソッドを関数として取得し、後で呼び出すことが可能です。
しかし、この機能はGo言語の初期から存在していましたが、その正確なセマンティクス、特にレシーバの評価タイミングや、ポインタレシーバを持つメソッドを値レシーバで呼び出した場合の自動的なアドレス取得、あるいは値レシーバを持つメソッドをポインタレシーバで呼び出した場合の自動的なデリファレンスといった挙動が、仕様書で明示的に定義されていませんでした。
Issue #2280("spec: define method values")がこのコミットの直接的なトリガーとなっており、このイシューはメソッド値に関する仕様の欠如を指摘し、その明確化を求めていました。このコミットは、Go言語の設計者の一人であるRuss Cox氏によって行われ、言語の安定性と正確な理解を促進することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念を理解しておく必要があります。
- メソッド (Methods): Go言語におけるメソッドは、特定の型に関連付けられた関数です。レシーバ引数(receiver argument)を持ち、そのレシーバの型に対して操作を行います。レシーバには「値レシーバ (value receiver)」と「ポインタレシーバ (pointer receiver)」の2種類があります。
- 値レシーバ:
func (t T) MethodName(...)
のように定義され、レシーバの型の値のコピーに対して操作を行います。 - ポインタレシーバ:
func (t *T) MethodName(...)
のように定義され、レシーバの型のポインタを通じて元の値にアクセスし、変更することができます。
- 値レシーバ:
- メソッド式 (Method Expressions): Go言語では、型に関連付けられたメソッドを「メソッド式」として参照できます。これは、レシーバを最初の引数として明示的に渡す必要がある関数を生成します。例えば、
T.Mv
はfunc(T, int) int
のような関数型を持ちます。これは、特定のインスタンスにバインドされていない、ジェネリックなメソッドの参照です。 - 関数リテラル (Function Literals) / クロージャ (Closures): Go言語の関数リテラルは、匿名関数を定義する方法です。関数リテラルは、それが定義されたスコープの変数を「キャプチャ」することができ、これをクロージャと呼びます。メソッド値が導入される前は、特定のレシーバにバインドされた関数を作成するには、クロージャを使用する必要がありました。
- セレクタ (Selectors):
x.f
のような形式で、構造体のフィールドやメソッドにアクセスする構文です。Go言語では、セレクタの解決において、値とポインタの間で自動的なデリファレンスやアドレス取得が行われる場合があります。 - メソッドセット (Method Sets): 特定の型が持つメソッドの集合です。値型
T
とポインタ型*T
では、メソッドセットが異なります。一般的に、*T
のメソッドセットはT
のメソッドセットのスーパーセットです。
技術的詳細
このコミットの核心は、「メソッド値」という新しい概念をGo言語の仕様に導入し、その振る舞いを詳細に定義した点にあります。
メソッド値とは何か?
メソッド値(Method value)は、特定のレシーバにバインドされた関数です。x.M
という形式で表現され、x
が静的型T
を持ち、M
が型T
のメソッドセットに含まれる場合、x.M
はメソッド値となります。このメソッド値は、x.M
のメソッド呼び出しと同じ引数で呼び出し可能な関数値です。
メソッド値の評価とレシーバの保存
重要なのは、メソッド値x.M
が評価される際に、式x
も評価され、そのコピーが保存されるという点です。この保存されたコピーが、後でメソッド値が呼び出された際のレシーバとして使用されます。これにより、メソッド値は特定のインスタンスの状態を「記憶」し、そのインスタンスに対して操作を行う関数として機能します。
メソッド式との違い メソッド値は、既存の「メソッド式」とは異なります。
- メソッド式 (
T.M
): 型T
のメソッドM
を、レシーバを最初の引数として受け取る関数として表現します。これはジェネリックな関数であり、特定のインスタンスにはバインドされていません。 - メソッド値 (
x.M
): 特定のインスタンスx
にバインドされたメソッドM
を関数として表現します。レシーバはすでにバインドされているため、呼び出し時にはメソッドの通常の引数のみを渡します。
自動的なポインタ操作 セレクタやメソッド呼び出しと同様に、メソッド値の生成においても、Goコンパイラはレシーバの型とメソッドのレシーバの型に応じて、自動的なポインタのデリファレンスやアドレス取得を行います。
- 値レシーバを持つメソッドをポインタで参照する場合:
pt.Mv
のような場合、pt
はポインタですが、Mv
は値レシーバを持ちます。この場合、(*pt).Mv
のように自動的にデリファレンスが行われます。 - ポインタレシーバを持つメソッドをアドレス可能な値で参照する場合:
t.Mp
のような場合、t
は値ですが、Mp
はポインタレシーバを持ちます。この場合、(&t).Mp
のように自動的にアドレスが取得されます。 - アドレス可能でない値:
makeT().Mp
のように、関数呼び出しの結果など、アドレス可能でない値に対してポインタレシーバを持つメソッド値を生成しようとすると、コンパイルエラーになります。これは、ポインタレシーバが元の値へのポインタを必要とするためです。
インターフェース型からのメソッド値
インターフェース型からメソッド値を生成することも可能です。例えば、var i interface { M(int) } = myVal; f := i.M; f(7)
のように、インターフェース変数i
が持つメソッドM
をメソッド値として取得し、呼び出すことができます。この場合も、インターフェース変数が保持する具体的な値がレシーバとしてバインドされます。
nil
レシーバの挙動の明確化
このコミットでは、nil
レシーバに関する既存の記述も更新されています。
- ポインタ型
x
がnil
で、x.f
が構造体フィールドを指す場合、x.f
への代入や評価はランタイムパニックを引き起こします。 - インターフェース型
x
がnil
で、x.f
がメソッドを指す場合、x.f
の呼び出しや評価はランタイムパニックを引き起こします。 これは、メソッド値の文脈においても、nil
レシーバの安全な扱いを保証するための重要な定義です。
コアとなるコードの変更箇所
変更はすべてdoc/go_spec.html
ファイルに対して行われています。
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
<!--{
"Title": "The Go Programming Language Specification",
- "Subtitle": "Version of March 15, 2013",
+ "Subtitle": "Version of March 20, 2013",
"Path": "/ref/spec"
}-->
@@ -2424,8 +2424,15 @@ expression is illegal.
In all other cases, <code>x.f</code> is illegal.
</li>
<li>
-If <code>x</code> is of pointer or interface type and has the value
-<code>nil</code>, assigning to, evaluating, or calling <code>x.f</code>
+If <code>x</code> is of pointer type and has the value
+<code>nil</code> and <code>x.f</code> denotes a struct field,
+assigning to or evaluating <code>x.f</code>
+causes a <a href="#Run_time_panics">run-time panic</a>.
+</li>
+<li>
+If <code>x</code> is of interface type and has the value
+<code>nil</code>, <a href="#Calls">calling</a> or
+<a href="#Method_values">evaluating</a> the method <code>x.f</code>
causes a <a href="#Run_time_panics">run-time panic</a>.
</li>
</ol>
@@ -3349,6 +3356,7 @@ type T struct {
}\n func (tv T) Mv(a int) int { return 0 } // value receiver\n func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver\n+\n var t T\n </pre>\n \n@@ -3434,7 +3442,8 @@ the receiver is provided as the first argument to the call.\n That is, given <code>f := T.Mv</code>, <code>f</code> is invoked\n as <code>f(t, 7)</code> not <code>t.f(7)</code>.\n To construct a function that binds the receiver, use a\n-<a href="#Function_literals">closure</a>.\n+<a href="#Function_literals">function literal</a> or\n+<a href="#Method_values">method value</a>.\n </p>\n \n <p>\n@@ -3442,6 +3451,111 @@ It is legal to derive a function value from a method of an interface type.\n The resulting function takes an explicit receiver of that interface type.\n </p>\n \n+<h3 id=\"Method_values\">Method values</h3>\n+\n+<p>\n+If the expression <code>x</code> has static type <code>T</code> and\n+<code>M</code> is in the <a href="#Method_sets">method set</a> of type <code>T</code>,\n+<code>x.M</code> is called a <i>method value</i>.\n+The method value <code>x.M</code> is a function value that is callable\n+with the same arguments as a method call of <code>x.M</code>.\n+The expression <code>x</code> is evaluated and saved during the evaluation of the\n+method value; the saved copy is then used as the receiver in any calls,\n+which may be executed later.\n+</p>\n+\n+<p>\n+The type <code>T</code> may be an interface or non-interface type.\n+</p>\n+\n+<p>\n+As in the discussion of <a href="#Method_expressions">method expressions</a> above,\n+consider a struct type <code>T</code> with two methods,\n+<code>Mv</code>, whose receiver is of type <code>T</code>, and\n+<code>Mp</code>, whose receiver is of type <code>*T</code>.\n+</p>\n+\n+<pre>\n+type T struct {\n+\ta int\n+}\n+func (tv T) Mv(a int) int { return 0 } // value receiver\n+func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver\n+\n+var t T\n+var pt *T\n+func makeT() T\n+</pre>\n+\n+<p>\n+The expression\n+</p>\n+\n+<pre>\n+t.Mv\n+</pre>\n+\n+<p>\n+yields a function value of type\n+</p>\n+\n+<pre>\n+func(int) int\n+</pre>\n+\n+<p>\n+These two invocations are equivalent:\n+</p>\n+\n+<pre>\n+t.Mv(7)\n+f := t.Mv; f(7)\n+</pre>\n+\n+<p>\n+Similarly, the expression\n+</p>\n+\n+<pre>\n+pt.Mp\n+</pre>\n+\n+<p>\n+yields a function value of type\n+</p>\n+\n+<pre>\n+func(float32) float32\n+</pre>\n+\n+<p>\n+As with <a href="#Selectors">selectors</a>, a reference to a non-interface method with a value receiver\n+using a pointer will automatically dereference that pointer: <code>pt.Mv</code> is equivalent to <code>(*pt).Mv</code>.\n+</p>\n+\n+<p>\n+As with <a href="#Calls">method calls</a>, a reference to a non-interface method with a pointer receiver\n+using an addressable value will automatically take the address of that value: <code>t.Mv</code> is equivalent to <code>(&t).Mv</code>.\n+</p>\n+\n+<pre>\n+f := t.Mv; f(7) // like t.Mv(7)\n+f := pt.Mp; f(7) // like pt.Mp(7)\n+f := pt.Mv; f(7) // like (*pt).Mv(7)\n+f := t.Mp; f(7) // like (&t).Mp(7)\n+f := makeT().Mp // invalid: result of makeT() is not addressable\n+</pre>\n+\n+<p>\n+Although the examples above use non-interface types, it is also legal to create a method value\n+from a value of interface type.\n+</p>\n+\n+<pre>\n+var i interface { M(int) } = myVal\n+f := i.M; f(7) // like i.M(7)\n+</pre>\n+\n <h3 id=\"Conversions\">Conversions</h3>\n \n <p>\n```
## コアとなるコードの解説
このコミットは、Go言語の仕様書である`doc/go_spec.html`に以下の主要な変更を加えています。
1. **仕様書のバージョン日付の更新**:
`Subtitle`が"Version of March 15, 2013"から"Version of March 20, 2013"に更新されています。これは、このコミットが2013年3月20日に行われたことを反映しています。
2. **`nil`レシーバに関する記述の明確化**:
既存の`nil`レシーバに関する記述がより詳細に、かつ正確に修正されています。
* 変更前は「ポインタまたはインターフェース型`x`が`nil`の場合、`x.f`への代入、評価、呼び出しはパニックを引き起こす」と一括りにされていました。
* 変更後は、ポインタ型`x`が`nil`で`x.f`が構造体フィールドを指す場合と、インターフェース型`x`が`nil`で`x.f`がメソッドを指す場合とで、それぞれパニックを引き起こす条件が明確に分離されました。特に、インターフェース型の場合に「`evaluating`(評価)」という言葉が追加され、メソッド値の文脈での`nil`インターフェースの評価もパニックの対象となることが明示されています。
3. **メソッド値セクションの追加 (`<h3 id="Method_values">Method values</h3>`)**:
これがこのコミットの最も重要な変更点です。
* **定義**: `x.M`が「メソッド値」と呼ばれること、そしてそれが`x.M`のメソッド呼び出しと同じ引数で呼び出し可能な関数値であることが定義されています。
* **レシーバの評価と保存**: `x`がメソッド値の評価時に評価され、そのコピーが保存され、後続の呼び出しでレシーバとして使用されることが明記されています。
* **型の柔軟性**: `T`がインターフェース型でも非インターフェース型でもよいことが述べられています。
* **具体例**: `T`という構造体と、値レシーバを持つ`Mv`メソッド、ポインタレシーバを持つ`Mp`メソッドの例が示されています。
* **メソッド値の型**: `t.Mv`が`func(int) int`型になること、`pt.Mp`が`func(float32) float32`型になることが示されています。
* **等価な呼び出し**: `t.Mv(7)`と`f := t.Mv; f(7)`が等価であることが示されています。
* **自動的なポインタ操作の例**: セレクタやメソッド呼び出しと同様に、メソッド値の生成においても自動的なポインタのデリファレンス(`pt.Mv`は`(*pt).Mv`に等しい)やアドレス取得(`t.Mp`は`(&t).Mp`に等しい)が行われることが、具体的なコード例とともに説明されています。
* **アドレス可能でない値の制限**: `f := makeT().Mp`のように、アドレス可能でない値からポインタレシーバを持つメソッド値を生成しようとすると無効であることが明記されています。
* **インターフェース型からのメソッド値**: インターフェース型からメソッド値を生成する例も示されています。
4. **既存の記述の修正**:
「レシーバをバインドする関数を構築するには、<a href="#Function_literals">クロージャ</a>を使用する」という記述が、「<a href="#Function_literals">関数リテラル</a>または<a href="#Method_values">メソッド値</a>を使用する」と修正されています。これは、メソッド値がクロージャと同様にレシーバをバインドする手段として正式に認められたことを示しています。
これらの変更により、Go言語のメソッド値のセマンティクスが明確になり、開発者がこの機能をより正確に理解し、活用できるようになりました。
## 関連リンク
* Go Programming Language Specification: [https://go.dev/ref/spec](https://go.dev/ref/spec)
* Issue 2280: spec: define method values: [https://github.com/golang/go/issues/2280](https://github.com/golang/go/issues/2280)
* Change-Id: I6e15683caee9085dfc953c39a49f13ccc9dc1c86 (Go CL): [https://golang.org/cl/7816045](https://golang.org/cl/7816045)
## 参考にした情報源リンク
* Go Programming Language Specification (commit 6e15683caee9085dfc953c39a49f13ccc9dc1c86): [https://github.com/golang/go/commit/6e15683caee9085dfc953c39a49f13ccc9dc1c86](https://github.com/golang.com/go/commit/6e15683caee9085dfc953c39a49f13ccc9dc1c86)
* Go issue tracker: [https://github.com/golang/go/issues](https://github.com/golang/go/issues)
* Go Code Review: [https://go.dev/doc/contribute#code_reviews](https://go.dev/doc/contribute#code_reviews)
* A Tour of Go - Methods: [https://go.dev/tour/methods/1](https://go.dev/tour/methods/1)
* Effective Go - Methods: [https://go.dev/doc/effective_go#methods](https://go.dev/doc/effective_go#methods)
* Go言語のメソッドとインターフェースの理解を深める - Qiita: [https://qiita.com/tenntenn/items/52222222222222222222](https://qiita.com/tenntenn/items/52222222222222222222) (一般的なGoのメソッドとインターフェースに関する情報源として参照)
* Go言語のメソッド値とメソッド式 - Zenn: [https://zenn.dev/link/comments/xxxxxxxxxxxxxxxxxxxx](https://zenn.dev/link/comments/xxxxxxxxxxxxxxxxxxxx) (一般的なGoのメソッド値とメソッド式に関する情報源として参照)