[インデックス 1144] ファイルの概要
このコミットは、Go言語のインターフェースにおけるメソッドの定義に関する重要な制約、すなわち「インターフェースのメソッドは完全な関数型を持たなければならない」というルールを導入または明確化するものです。具体的には、インターフェースのメソッド宣言が、単なる型ではなく、引数と戻り値を含む完全な関数シグネチャを持つ必要があることを強制するための変更です。この変更は、Go言語の初期段階における型システムの厳密性を高める一環として行われました。
コミット
commit d58cd7626e8abf1d0e691bf169d06b17b1a53cef
Author: Robert Griesemer <gri@golang.org>
Date: Mon Nov 17 16:37:13 2008 -0800
- interface methods must have full function type
R=r
OCL=19410
CL=19410
---
test/bugs/bug121.go | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/test/bugs/bug121.go b/test/bugs/bug121.go
new file mode 100644
index 0000000000..cc960e318c
--- /dev/null
+++ b/test/bugs/bug121.go
@@ -0,0 +1,25 @@
+// errchk $G $D/$F.go
+//
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+type T ()
+
+type I interface {
+ f, g ();
+ h T; // should only allow FunctionType here
+}
+
+type S struct {
+}
+
+func (s *S) f() {}
+func (s *S) g() {}
+func (s *S) h() {} // here we can't write (s *S) T either
+
+func main() {
+ var i I = new(S);
+}
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d58cd7626e8abf1d0e691bf169d06b17b1a53cef
元コミット内容
- interface methods must have full function type
変更の背景
このコミットは、Go言語のインターフェース定義における型チェックの厳密化を目的としています。Go言語の初期開発段階において、インターフェースのメソッド宣言が、単に型名のみで記述されることを許容していた可能性があります。しかし、インターフェースのメソッドは、具体的な型がそのインターフェースを実装する際に、特定のシグネチャ(引数と戻り値の型)を持つ関数として定義される必要があります。
このコミット以前は、type I interface { h T; }
のような記述が許されていたかもしれません。しかし、これはGoの型システムの意図と矛盾します。インターフェースは「振る舞い」を定義するものであり、その振る舞いはメソッドのシグネチャによって表現されます。h T;
のような記述は、h
がT
型のフィールドであるかのような誤解を招く可能性があり、また、T
が関数型でない場合に、そのインターフェースを実装する具体的な型がどのようなメソッドを持つべきか不明確になります。
この変更は、インターフェースのメソッドが常に明確な関数シグネチャを持つことを強制することで、Goの型システムの整合性と堅牢性を向上させるために導入されました。これにより、コンパイラはインターフェースの実装をより正確に検証できるようになり、開発者はより予測可能なコードを書くことができます。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。インターフェースは、オブジェクトがどのような振る舞いを持つべきかを記述しますが、その振る舞いの具体的な実装は提供しません。Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たしているとみなされます。明示的なimplements
キーワードは不要です。
例:
type Reader interface {
Read(p []byte) (n int, err error)
}
このReader
インターフェースは、Read
というメソッドを持つことを定義しています。Read
メソッドは[]byte
型の引数p
を取り、int
型のn
とerror
型のerr
を返します。
メソッドと関数型
Goにおいて、メソッドは特定の型に関連付けられた関数です。メソッドはレシーバ(func (s *S) f() {}
の (s *S)
の部分)を持ちます。
「関数型」とは、関数のシグネチャ(引数の型と戻り値の型)を定義する型のことです。例えば、func(int, string) (bool, error)
は関数型です。
インターフェースのメソッド宣言は、本質的に関数型を記述するものです。例えば、Read(p []byte) (n int, err error)
は、Read
という名前のメソッドが、[]byte
を引数に取り、int
とerror
を返す関数であることを示しています。
技術的詳細
このコミットの技術的詳細の核心は、Goコンパイラがインターフェースのメソッド宣言をどのように解釈し、検証するかという点にあります。
Go言語の設計思想の一つに「明示性」と「シンプルさ」があります。インターフェースのメソッドは、そのインターフェースを実装する型が提供すべき「振る舞い」を明確に定義する必要があります。この「振る舞い」は、メソッドの名前、引数の型、そして戻り値の型によって完全に記述されます。
もしインターフェースのメソッド宣言が完全な関数型を持たない場合、例えば type I interface { h T; }
のように、h
がT
という型を持つことを示唆するだけでは、以下の問題が生じます。
- 曖昧性:
h T;
がh
という名前のメソッドで、T
型の引数を取るのか、T
型の戻り値を返すのか、あるいはh
がT
型のフィールドなのか、といった曖昧さが生じます。Goのインターフェースはフィールドを定義しないため、これは設計意図に反します。 - 実装の不確実性: インターフェースを実装する型は、
h
という名前のメソッドをどのように実装すれば良いのかが不明確になります。引数や戻り値の型が指定されていないため、コンパイラは実装がインターフェースの要件を満たしているかを検証できません。 - 型システムの整合性: Goの型システムは静的型付けであり、コンパイル時に厳密な型チェックを行います。インターフェースのメソッド宣言が完全な関数型でない場合、この厳密な型チェックが困難になり、実行時エラーのリスクを高める可能性があります。
このコミットは、コンパイラがインターフェースのメソッド宣言をパースする際に、それが必ずFunctionName (parameters) (results)
という形式の「関数型」として認識されるように制約を課したことを示唆しています。これにより、h T;
のような不正な宣言はコンパイルエラーとなり、開発者はインターフェースの定義を正しく記述するよう強制されます。
test/bugs/bug121.go
のコメント// should only allow FunctionType here
は、この制約が導入されたことを明確に示しています。これは、Go言語の初期段階で、インターフェースの構文がまだ完全に固まっていなかった時期に、このような曖昧な記述を許容しないようにするための重要な修正であったと考えられます。
コアとなるコードの変更箇所
このコミットでは、test/bugs/bug121.go
という新しいテストファイルが追加されています。これは、インターフェースのメソッドが完全な関数型を持たなければならないという新しい(または明確化された)ルールを検証するためのものです。
--- /dev/null
+++ b/test/bugs/bug121.go
@@ -0,0 +1,25 @@
+// errchk $G $D/$F.go
+//
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+type T ()
+
+type I interface {
+ f, g ();
+ h T; // should only allow FunctionType here
+}
+
+type S struct {
+}
+
+func (s *S) f() {}
+func (s *S) g() {}
+func (s *S) h() {} // here we can't write (s *S) T either
+
+func main() {
+ var i I = new(S);
+}
コアとなるコードの解説
追加されたtest/bugs/bug121.go
ファイルは、Goコンパイラがインターフェースのメソッド宣言の妥当性を正しくチェックするかどうかをテストするためのものです。
// errchk $G $D/$F.go
: この行は、このファイルがコンパイルエラーを発生させることを期待するテストであることを示しています。$G
はGoコンパイラ、$D/$F.go
は現在のファイルのパスを指します。type T ()
: これは空の構造体型T
を定義しています。この型自体は特に意味を持ちませんが、テストケースでインターフェースのメソッド宣言に不正に使用されます。type I interface { ... }
: インターフェースI
が定義されています。f, g ();
: これはf()
とg()
という2つのメソッドを定義しています。これらは引数も戻り値もない関数型であり、Goのインターフェースメソッドとして正しい形式です。h T;
: この行がこのテストの核心です。 ここではh
という名前のメソッドがT
型を持つと宣言されています。しかし、Goのインターフェースメソッドは完全な関数シグネチャ(引数と戻り値を含む)を持つ必要があります。単に型名T
を記述するだけでは、これは有効な関数型ではありません。コメント// should only allow FunctionType here
が、この行がコンパイルエラーを引き起こすことを意図していることを明確に示しています。
type S struct { ... }
: 空の構造体型S
が定義されています。func (s *S) f() {}
,func (s *S) g() {}
,func (s *S) h() {}
: 型S
にf
,g
,h
という3つのメソッドが定義されています。これらのメソッドは、インターフェースI
のメソッド名と一致しています。特にh()
メソッドは、インターフェースI
内の不正なh T;
宣言に対応する形で定義されています。コメント// here we can't write (s *S) T either
は、S
のメソッド定義においても、h
がT
型を持つというような曖昧な記述はできないことを示唆しています。func main() { var i I = new(S); }
:main
関数内で、S
型の新しいインスタンスをI
インターフェース型の変数i
に代入しようとしています。もしインターフェースI
の定義が不正であれば、この代入はコンパイルエラーになります。
このテストファイルは、h T;
というインターフェースメソッドの宣言がGoコンパイラによって不正と判断され、コンパイルエラーとなることを確認するために書かれています。これにより、Go言語のインターフェースのメソッドは、常に明確な関数シグネチャを持つことが保証されます。
関連リンク
- Go言語仕様 (The Go Programming Language Specification): インターフェースの定義に関する公式ドキュメント。
- Go言語の初期の設計に関する議論やメーリングリストのアーカイブ(もし公開されていれば、このコミットに関連する議論が見つかる可能性があります)。
参考にした情報源リンク
- Go言語の公式ドキュメントおよび言語仕様。
- Go言語のソースコードリポジトリ(特に
go/src/cmd/compile
やgo/src/go/types
パッケージの初期のコミット履歴)。 - Go言語に関する一般的な知識とインターフェースの概念。