[インデックス 11550] ファイルの概要
このコミットは、Go言語の標準ライブラリである append
関数の挙動に関するテストケースを追加するものです。具体的には、異なる名前付き型(named types)でありながら、基底となる要素型(element type)が同じスライス型同士を append
関数に渡した場合のコンパイラの振る舞いを検証しています。
コミット
test: test append with two different named types with same element type
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5615045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3692726f32f4cff4429e893830871d9b50b9816b
元コミット内容
test: test append with two different named types with same element type
このコミットは、append
関数が、要素型が同じであるものの、異なる名前を持つスライス型を引数として受け入れるかどうかをテストします。
変更の背景
Go言語の append
関数は、第一引数にスライス、第二引数にそのスライスの要素型と同じ型の可変長引数(またはスライス)を取ります。このコミットが追加された背景には、Goの型システムにおける「名前付き型」と「基底型」の概念、そして append
関数が引数の型をどのように解釈するかという点があります。
Goでは、既存の型に新しい名前を付けて新しい型を定義することができます(type MyInt int
のように)。このような名前付き型は、基底型が同じであっても、異なる型として扱われます。しかし、append
のような特定の組み込み関数においては、引数の型が厳密に一致しなくても、特定の条件(この場合は要素型の一致)を満たせば受け入れられる場合があります。
このコミットは、このような微妙な型互換性の挙動が期待通りに機能するかどうかを確認するためのテストの不足を補うものです。特に、異なる名前付きスライス型が、append
の第二引数として渡された場合にコンパイラがエラーを出さずに処理できることを保証するために追加されました。
前提知識の解説
Go言語のスライス (Slice)
Go言語のスライスは、配列をラップした動的なデータ構造です。スライスは []Type
の形式で宣言され、Type
はスライスの要素の型を示します。スライスは参照型であり、基底となる配列の一部を参照します。
Go言語の名前付き型 (Named Types)
Goでは、type NewType UnderlyingType
の構文を使って新しい型を定義できます。この NewType
は「名前付き型」と呼ばれ、UnderlyingType
はその「基底型」です。
例:
type MyInt int
type MyString string
type MySlice []int
MyInt
は int
とは異なる型として扱われます。たとえ基底型が同じでも、異なる名前付き型の間には暗黙的な変換は行われません。明示的な型変換が必要です。
append
関数の挙動
Goの組み込み関数 append
は、スライスに要素を追加するために使用されます。そのシグネチャは概念的に以下のようになります(実際にはジェネリックに動作します):
func append(slice []T, elems ...T) []T
ここで T
はスライスの要素型です。重要なのは、第二引数 elems
が ...T
または []T
の形式で、第一引数のスライス slice
の要素型 T
と互換性がある必要があるという点です。
このコミットのポイントは、append
の第二引数に渡されるスライスが、第一引数のスライスと「同じ基底型を持つが、異なる名前付き型である」場合にどうなるか、という点です。Goの仕様では、append(s []T, x ...U)
の形式で、U
が T
に代入可能であるか、または U
が []T
に代入可能なスライス型である場合に、append
が機能することが許容されています。このテストケースは、後者の「U
が []T
に代入可能なスライス型である」という条件を、名前付き型が絡むケースで検証しています。
技術的詳細
このコミットで追加されたテストケース verifyType()
は、Goの型システムにおけるスライスの互換性、特に append
関数の引数としての振る舞いを検証しています。
-
名前付きスライス型の定義:
type T1 []int type T2 []int
ここで
T1
とT2
は、どちらも基底型が[]int
である異なる名前付き型として定義されています。Goの型システムでは、これらは異なる型として扱われます。例えば、T1
型の変数をT2
型の変数に直接代入することはできません。 -
append
関数の呼び出し:t1 := T1{1} t2 := T2{2} verify("T1", append(t1, t2...), T1{1, 2})
ここで
append(t1, t2...)
が呼び出されています。t1
はT1
型(基底型[]int
)。t2
はT2
型(基底型[]int
)。t2...
はt2
スライスの要素を個別の引数として展開します。
append
関数の仕様では、第二引数(可変長引数)がスライスとして渡される場合、そのスライスは第一引数のスライスの要素型と同じ要素型を持つ必要があります。このケースでは、t1
の要素型はint
であり、t2
の要素型もint
です。したがって、append
はT1
とT2
が異なる名前付き型であっても、その基底となる要素型が一致するため、この操作を許可します。 -
戻り値の型:
append
関数の戻り値の型は、常に第一引数のスライスと同じ型になります。この場合、append(t1, t2...)
の結果はT1
型のスライスになります。テストでは、期待される結果T1{1, 2}
と比較することで、この挙動が正しいことを確認しています。
このテストは、Goコンパイラが append
の型チェックにおいて、名前付き型の厳密な一致ではなく、基底となる要素型の互換性を考慮していることを明確に示しています。これは、Goの型システムが柔軟性と厳密性のバランスを取っている一例と言えます。
コアとなるコードの変更箇所
変更は test/append.go
ファイルに対して行われました。
-
main
関数にverifyType()
の呼び出しが追加されました。--- a/test/append.go +++ b/test/append.go @@ -27,6 +27,7 @@ func main() { } verifyStruct() verifyInterface() +\tverifyType()\n }
-
新しいテスト関数
verifyType()
が追加されました。--- a/test/append.go +++ b/test/append.go @@ -230,3 +231,17 @@ func verifyInterface() {\n verify("interface l", append(s), s)\n verify("interface m", append(s, e...), r)\n }\n +\n +type T1 []int\n +type T2 []int\n +\n +func verifyType() {\n +\t// The second argument to append has type []E where E is the\n +\t// element type of the first argument. Test that the compiler\n +\t// accepts two slice types that meet that requirement but are\n +\t// not assignment compatible. The return type of append is\n +\t// the type of the first argument.\n +\tt1 := T1{1}\n +\tt2 := T2{2}\n +\tverify("T1", append(t1, t2...), T1{1, 2})\n +}\n
コアとなるコードの解説
追加された verifyType
関数は以下の要素で構成されています。
-
名前付きスライス型の定義:
type T1 []int type T2 []int
T1
とT2
は、どちらも[]int
を基底型とする新しい名前付きスライス型です。これにより、Goの型システムがこれらを異なる型として扱うことを保証します。 -
変数の初期化:
t1 := T1{1} t2 := T2{2}
t1
はT1
型のスライスとして[1]
で初期化され、t2
はT2
型のスライスとして[2]
で初期化されます。 -
append
関数のテスト:verify("T1", append(t1, t2...), T1{1, 2})
append(t1, t2...)
: ここがテストの核心です。t1
はT1
型のスライス、t2...
はT2
型のスライスの要素を展開したものです。Goのappend
関数は、第二引数がスライスとして渡される場合、そのスライスの要素型が第一引数のスライスの要素型と一致していれば、異なる名前付き型であっても受け入れます。この場合、T1
の要素型もint
、T2
の要素型もint
であるため、この呼び出しは有効です。verify(...)
: これはテストフレームワークの一部であり、append
の結果が期待される値T1{1, 2}
と一致するかどうかを検証します。append
の結果は第一引数t1
の型であるT1
型のスライスになります。
このテストは、Goコンパイラが append
の型チェックにおいて、名前付き型の厳密な同一性ではなく、基底となる要素型の互換性を適切に判断していることを確認するための重要なケースです。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- Go言語の
append
関数に関するドキュメント: https://pkg.go.dev/builtin#append - このコミットが属するGoの変更リスト (CL): https://golang.org/cl/5615045
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
src/builtin/builtin.go
やコンパイラの型チェック関連コード) - Go言語の型システムに関する一般的な情報源 (例: Effective Go, Go言語の仕様書)
- GitHub上のGoリポジトリ: https://github.com/golang/go