[インデックス 1427] ファイルの概要
このコミットは、Go言語のテストスイート内の test/complit.go
ファイルにおける、複合リテラル(composite literal)の誤った使用方法を修正するものです。具体的には、スライスや配列の複合リテラルに対して不適切にアドレス演算子 &
を適用していた箇所や、配列の長さ指定に関する問題を修正し、テストが正しく動作するようにしています。
コミット
commit 4d194b90564e37e3d35ed682f5935e154338a995
Author: Rob Pike <r@golang.org>
Date: Tue Jan 6 20:17:58 2009 -0800
18 tests are behaving incorrectly
no more surprises - all caught up
R=rsc
DELTA=4 (0 added, 0 deleted, 4 changed)
OCL=22194
CL=22194
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4d194b90564e37e3d35ed682f5935e154338a995
元コミット内容
このコミットの元のメッセージは以下の通りです。
18 tests are behaving incorrectly no more surprises - all caught up
これは、当時18個のテストが正しく動作していなかったことを示しており、このコミットによってそれらの問題が解決され、テストが全て正常に動作するようになったことを意味しています。
変更の背景
Go言語は当時まだ開発の初期段階にあり、言語仕様やコンパイラの挙動が頻繁に調整されていました。このコミットは、複合リテラル、特にスライスや配列の初期化におけるポインタの扱いに関する初期の設計上の問題、あるいはその誤用が原因で発生していたテストの失敗を修正するために行われました。
複合リテラルは、Goにおいて構造体、配列、スライス、マップなどの複合型を簡潔に初期化するための強力な機能です。しかし、その初期の段階では、ポインタの取得(&
演算子)と複合リテラルの組み合わせに関するセマンティクスがまだ完全に固まっていなかったか、あるいは開発者による誤解があった可能性があります。このコミットは、これらの誤用を修正し、言語の意図する挙動にテストを合わせることで、コンパイラやランタイムの安定性を確保することを目的としています。
前提知識の解説
複合リテラル (Composite Literals)
Go言語における複合リテラルは、構造体、配列、スライス、マップなどの複合型の値を直接生成するための構文です。 例:
- スライス:
[]int{1, 2, 3}
- 配列:
[3]int{1, 2, 3}
- 構造体:
struct{x int}{x: 10}
複合リテラルは、その場で新しい値を生成します。重要なのは、これらのリテラルが生成するのは「値」であり、通常は「変数」ではないという点です。
スライス (Slices)
スライスはGo言語における動的な配列のようなものです。内部的には、基盤となる配列へのポインタ、長さ、容量の3つの要素から構成されます。スライス自体は参照型であり、変数を介して基盤配列の一部を参照します。
配列 (Arrays)
配列は固定長のシーケンスです。Goの配列は値型であり、その長さは型の一部です。
例: [3]int
は長さ3の整数配列の型です。
ポインタ (Pointers)
Goのポインタは、変数のメモリアドレスを保持する変数です。&
演算子は変数のアドレスを取得するために使用されます。
例: p := &x
は変数 x
のアドレスを p
に代入します。
...
演算子と配列の長さ推論
Goの配列リテラルでは、...
を使用して配列の長さを初期化子リストの要素数から推論させることができます。
例: [...]int{1, 2, 3}
は [3]int{1, 2, 3}
と同じ意味になります。これは、要素数を手動で数える手間を省き、コードの保守性を高めるために便利です。
技術的詳細
このコミットの技術的詳細は、Go言語の複合リテラルとポインタのセマンティクス、特に初期のGoにおけるそれらの厳密な適用に焦点を当てています。
-
複合リテラルへの
&
演算子の適用:- 変更前:
oai = &[]int{1,2,3};
およびeq(&[]*R{itor(0), ...});
- 変更後:
oai = []int{1,2,3};
およびeq([]*R{itor(0), ...});
- Go言語では、複合リテラルは「値」を生成します。これらの値は、通常、アドレスを持つ「変数」ではありません。したがって、複合リテラルに対して直接
&
演算子を適用してそのアドレスを取得しようとすると、コンパイルエラーになるか、未定義の動作を引き起こす可能性があります。 - スライスは参照型であり、スライス変数自体は基盤配列へのポインタを含みますが、
[]int{1,2,3}
という複合リテラル自体は、その場で生成されるスライス値です。このスライス値のアドレスを取ることは、Goの設計思想に反するか、あるいは初期のコンパイラがこれを正しく扱えなかった可能性があります。 - この修正は、複合リテラルから直接アドレスを取得するのではなく、生成されたスライス値をそのまま使用するように変更しています。これにより、Goの型システムとメモリモデルに合致した正しい挙動になります。
- 変更前:
-
配列の長さ推論
...
の使用:- 変更前:
at := []*T{&t, &t, &t};
- 変更後:
at := [...]*T{&t, &t, &t};
- 変更前は、
[]*T
となっており、これはスライスの型宣言です。しかし、初期化子リストが与えられているため、これは配列リテラルとして解釈されるべきでした。Goの初期のバージョンでは、この構文が曖昧であったか、あるいはコンパイラが正しく配列として推論できなかった可能性があります。 [...]*T
と明示的に...
を使用することで、コンパイラは初期化子リストの要素数(この場合は3つ)に基づいて配列の長さを正確に推論し、[3]*T
型の配列としてat
を初期化します。これにより、コードの意図が明確になり、コンパイラが正しく型を推論できるようになります。
- 変更前:
-
パニックメッセージの修正:
- 変更前:
if len(aat) != 2 || len(aat[1]) != 3 { panic("at") }
- 変更後:
if len(aat) != 2 || len(aat[1]) != 3 { panic("aat") }
- これは単純なタイポ修正です。
aat
という変数に関するパニックメッセージが誤って"at"
となっていたのを、正しい変数名"aat"
に修正しています。これは論理的なバグ修正ではなく、デバッグ時のメッセージの正確性を向上させるためのものです。
- 変更前:
これらの変更は、Go言語の初期段階における型システムとコンパイラの成熟度を反映しており、言語のセマンティクスがより厳密に定義され、実装されていく過程を示しています。
コアとなるコードの変更箇所
test/complit.go
ファイルにおいて、以下の4箇所が変更されています。
-
スライス初期化におけるアドレス演算子の削除:
--- a/test/complit.go +++ b/test/complit.go @@ -42,10 +42,10 @@ func main() { //if len(a3) != 10 || a2[3] != 0 { panic("a3") } var oai []int; - oai = &[]int{1,2,3}; + oai = []int{1,2,3}; if len(oai) != 3 { panic("oai") }
-
配列初期化における
...
演算子の追加:--- a/test/complit.go +++ b/test/complit.go @@ -42,10 +42,10 @@ func main() { if len(oai) != 3 { panic("oai") } - at := []*T{&t, &t, &t}; + at := [...]*T{&t, &t, &t}; if len(at) != 3 { panic("at") }
-
パニックメッセージの修正:
--- a/test/complit.go +++ b/test/complit.go @@ -53,7 +53,7 @@ func main() { if len(ac) != 3 { panic("ac") } aat := [][len(at)]*T{at, at}; - if len(aat) != 2 || len(aat[1]) != 3 { panic("at") } + if len(aat) != 2 || len(aat[1]) != 3 { panic("aat") } s := string([]byte{'h', 'e', 'l', 'l', 'o'}); if s != "hello" { panic("s") }
-
関数呼び出しにおけるアドレス演算子の削除:
--- a/test/complit.go +++ b/test/complit.go @@ -59,7 +59,7 @@ func main() { if len(m) != 3 { panic("m") } - eq(&[]*R{itor(0), itor(1), itor(2), itor(3), itor(4), itor(5)}); + eq([]*R{itor(0), itor(1), itor(2), itor(3), itor(4), itor(5)}); p1 := NewP(1, 2); p2 := NewP(1, 2);
コアとなるコードの解説
1. oai = &[]int{1,2,3};
から oai = []int{1,2,3};
への変更
- 変更前:
&[]int{1,2,3}
は、スライス複合リテラル[]int{1,2,3}
のアドレスを取ろうとしています。Go言語では、複合リテラルは一時的な値を生成し、その値は通常、アドレスを持つ変数ではありません。したがって、この操作は不正であるか、意図しない挙動を引き起こす可能性がありました。 - 変更後:
[]int{1,2,3}
は、直接スライス値を生成し、それをoai
に代入しています。oai
はスライス型 ([]int
) であり、スライスは参照型であるため、この代入はスライスヘッダ(基盤配列へのポインタ、長さ、容量)をコピーするだけで、正しい動作です。この修正により、Goの型システムとメモリモデルに準拠したスライスの初期化が行われます。
2. at := []*T{&t, &t, &t};
から at := [...]*T{&t, &t, &t};
への変更
- 変更前:
[]*T
はスライスの型宣言です。しかし、右辺で初期化子リストが与えられているため、これは配列リテラルとして解釈されるべきでした。Goの初期のコンパイラは、この構文を配列として正しく推論できなかったか、あるいはスライスとして誤って解釈していた可能性があります。 - 変更後:
[...]*T
とすることで、コンパイラに「これは配列であり、その長さは初期化子リストの要素数(この場合は3)から推論せよ」と明示的に指示しています。これにより、at
は[3]*T
型の配列として正しく初期化され、コードの意図が明確になります。
3. panic("at")
から panic("aat")
への変更
- これは、
aat
という変数に関する条件が満たされなかった場合に発生するパニックメッセージの修正です。元のコードでは誤って"at"
となっていましたが、aat
の検証を行っているため、メッセージもaat
に関連するものに修正されました。これは機能的な変更ではなく、デバッグ時の情報提供の正確性を高めるための修正です。
4. eq(&[]*R{itor(0), ...});
から eq([]*R{itor(0), ...});
への変更
- この変更は、1番目の変更と同様の理由です。
eq
関数に渡される引数が、[]*R
型のスライス複合リテラルです。ここでも、複合リテラルのアドレスを取る&
演算子が不適切に使用されていました。 eq([]*R{itor(0), ...})
とすることで、スライス値を直接関数に渡しています。これにより、Goのセマンティクスに合致し、テストが正しく実行されるようになります。
これらの修正は、Go言語の初期段階における言語仕様の洗練と、コンパイラの堅牢性の向上を示すものです。特に、複合リテラルとポインタの相互作用に関する厳密なルールが確立されていく過程を垣間見ることができます。
関連リンク
- Go言語の複合リテラルに関する公式ドキュメント(現在のバージョン): https://go.dev/ref/spec#Composite_literals
- Go言語のスライスに関する公式ドキュメント(現在のバージョン): https://go.dev/blog/slices-intro
参考にした情報源リンク
- Go言語の公式ドキュメント (Go Language Specification)
- Go言語のブログ記事やチュートリアル(複合リテラル、スライス、配列に関する基本的な概念)
- Go言語のGitHubリポジトリのコミット履歴とdiff
- Go言語の初期の設計に関する議論(Go mailing list archivesなど、もしアクセス可能であれば)