Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1404] ファイルの概要

このコミットは、Go言語のテストスイート内のいくつかの既存のバグテストをクリーンアップし、更新することを目的としています。具体的には、test/fixedbugsディレクトリ内の5つのファイル(bug027.go, bug045.go, bug054.go, bug097.go, bug119.go)に対して変更が加えられています。これらの変更は、Go言語の初期開発段階における型システム、特に配列、スライス、およびポインタの扱いの進化を反映していると考えられます。

コミット

commit 25444d079fde1c579cf2a29f20f623533324ccaf
Author: Rob Pike <r@golang.org>
Date:   Mon Jan 5 13:09:34 2009 -0800

    clean up some tests
    
    R=rsc
    DELTA=10  (1 added, 2 deleted, 7 changed)
    OCL=22033
    CL=22033

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/25444d079fde1c579cf2a29f20f623533324ccaf

元コミット内容

このコミットの元の内容は「clean up some tests」であり、Go言語のテストコードの整理と改善が目的であることが示唆されています。具体的な変更は、既存のバグテストファイルにおける型宣言や初期化の方法の調整に焦点を当てています。

変更の背景

このコミットは2009年1月5日に行われており、Go言語がまだ活発に開発されていた非常に初期の段階に当たります。この時期は、Go言語の設計、特に型システムやメモリ管理のセマンティクスが固まりつつあった時期です。したがって、「clean up some tests」というコミットメッセージは、言語仕様の変更や、よりイディオマティックなGoコードの書き方が確立されるにつれて、既存のテストコードを新しい標準に合わせる必要があったことを示唆しています。

特に、配列とスライスの区別、ポインタの利用方法、そしてnew組み込み関数の挙動に関する理解が深まるにつれて、以前のテストコードが古くなったり、意図しない挙動を示したりする可能性がありました。このコミットは、これらのテストがGo言語の最新のセマンティクスに正しく準拠し、意図したバグを正確にテストできるようにするための調整であると考えられます。

前提知識の解説

このコミットの変更を理解するためには、Go言語の初期の型システム、特に以下の概念についての知識が必要です。

  1. 配列 (Arrays): Goにおける配列は、固定長で要素の型が同じシーケンスです。宣言時にサイズが決定され、そのサイズは型の不可欠な一部となります(例: [10]Element)。
  2. スライス (Slices): スライスは、配列の上に構築された動的なビューです。長さと容量を持ち、実行時にサイズを変更できます。スライスは配列への参照であり、配列全体をコピーすることなく、配列の一部を参照できます(例: []Element)。make関数やnew関数と組み合わせて初期化されることが一般的です。
  3. ポインタ (Pointers): GoはC/C++のようなポインタをサポートしますが、ポインタ演算は許可されていません。ポインタは変数のメモリアドレスを保持し、*演算子で間接参照(デリファレンス)して値にアクセスします。&演算子で変数のアドレスを取得します。
  4. new組み込み関数: new(T)は、型Tのゼロ値に初期化された新しい項目を割り当て、その項目へのポインタを返します。これは、メモリを割り当ててポインタを返すためのものです。
  5. make組み込み関数: make(T, args)は、スライス、マップ、チャネルなどの組み込み型を初期化するために使用されます。newとは異なり、makeはゼロ値ではなく、適切な内部データ構造(例えば、スライスの場合は基になる配列)を割り当てて初期化された(ただし要素はゼロ値)型Tの値を返します。

このコミットの変更は、newmakeの使い分け、配列とスライスのセマンティクスの明確化、そしてポインタの適切な利用方法に関するGo言語の進化を反映していると見られます。特に、new([10]Element)からnew([]Element, 10)への変更は、固定長配列のポインタから、容量を指定したスライスのポインタへの移行を示唆しており、Go言語がスライスをより推奨するデータ構造として確立していく過程を反映しています。

技術的詳細

このコミットは、主にGo言語の型システム、特に配列、スライス、およびポインタの初期化と使用方法に関するテストコードの修正を含んでいます。

  1. test/fixedbugs/bug027.go および test/fixedbugs/bug054.go の変更:

    • v.elem = new([10]Element); から v.elem = new([]Element, 10); へ変更されています。
    • これは、Vector構造体のelemフィールドが、固定サイズの配列[10]Elementへのポインタとして初期化されていたのを、容量10のスライス[]Elementへのポインタとして初期化するように変更されたことを意味します。
    • 初期のGoでは、newが配列のポインタを返す場合と、スライスのポインタを返す場合のセマンティクスが混在していた可能性があります。この変更は、スライスを動的なコレクションとして扱うためのより適切な初期化方法への移行を示しています。new([]Element, 10)は、[]Element型のゼロ値(nilスライス)へのポインタを返しますが、このコンテキストではおそらくmake([]Element, 0, 10)のような意図で使われていたか、あるいは初期のnewの挙動が現在とは異なっていた可能性があります。しかし、現在のGoではスライスを初期化する際にはmakeを使うのが一般的です。この変更は、newがポインタを返すという性質を維持しつつ、スライス型を扱う方向への調整と見られます。
    • また、bug027.goでは、type I struct { val int; }; // BUG: can't be local; というコメントアウトされた行が削除され、func main() { type I struct { val int; }; のように、main関数内でローカルに構造体Iが定義されています。これは、初期のGoにおけるローカル型定義の制約が緩和されたか、あるいはテストの意図が変更されたことを示唆しています。
  2. test/fixedbugs/bug045.go の変更:

    • ta = new([1]*T); から ta = *new(*[1]*T); // TODO: the first * shouldn't be necessary へ変更されています。
    • これは、[1]*T*T型の要素を1つ持つ配列)へのポインタを割り当てる代わりに、*[1]*T[1]*T型へのポインタ)へのポインタを割り当て、それをデリファレンスしてtaに代入するという、より複雑なポインタ操作を行っています。
    • コメントにある「TODO: the first * shouldn't be necessary」は、この時点でのGoコンパイラや型推論の挙動がまだ不安定であったか、あるいはより簡潔な構文が将来的に導入されることを期待していたことを示しています。これは、Go言語の型システムが成熟していく過程で、ポインタの扱いがより直感的になるように改善されていったことを示唆しています。
  3. test/fixedbugs/bug097.go の変更:

    • var a [3]*A; から var a [3]A; へ変更されています。
    • a[i] = &A{i}; から a[i] = A{i}; へ変更されています。
    • これは、A型([]intのスライス)のポインタの配列[3]*Aを宣言していたのを、A型([]intのスライス)の配列[3]Aを宣言するように変更したことを意味します。
    • 以前はスライスA自体ではなく、そのポインタを配列に格納していましたが、変更後はスライスAの値を直接配列に格納しています。これは、スライスが値型として扱われるGoのセマンティクスに合わせた変更であり、スライスのコピーが意図される場合にポインタを介さずに直接値を扱うようになったことを示しています。
  4. test/fixedbugs/bug119.go の変更:

    • return (*a)[0] から return a[0] へ変更されています。
    • if x := foo(a) ; x != 12 { panicln(3) } から if x := foo(*a) ; x != 12 { panicln(3) } へ変更されています。
    • foo関数は[]int型のスライスを引数に取ります。以前はa&[]int{12}(スライスへのポインタ)として宣言されていたため、foo(a)とするとポインタを直接渡していました。しかし、fooはスライスを期待しているため、(*a)[0]のようにデリファレンスして要素にアクセスしたり、foo(*a)のようにデリファレンスしたスライスを関数に渡すように修正されています。
    • これは、Go言語におけるポインタと値のセマンティクス、特にスライスが値として渡される(ただし、基になる配列は共有される)という挙動をより正確に反映するための修正です。

これらの変更は全体として、Go言語の型システムが初期の試行錯誤の段階から、より明確でイディオマティックな使用パターンへと進化していく過程を示しています。特に、スライスがGoにおける動的なコレクションの主要な手段として確立され、その初期化と利用方法が洗練されていったことが伺えます。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は、Go言語のテストファイルにおける型宣言、変数初期化、および関数呼び出しの引数渡しに関するものです。

  1. test/fixedbugs/bug027.go および test/fixedbugs/bug054.go:

    --- a/test/fixedbugs/bug027.go
    +++ b/test/fixedbugs/bug027.go
    @@ -17,7 +17,7 @@ type Vector struct {
     func New() *Vector {
      	v := new(*Vector);
      	v.nelem = 0;
    - 	v.elem = new([10]Element);
    + 	v.elem = new([]Element, 10);
      	return v;
      }
      
    @@ -30,9 +30,8 @@ func (v *Vector) Insert(e Element) {
      	v.nelem++;
      }
      
    -type I struct { val int; };  // BUG: can\'t be local;
    -
     func main() {
    + 	type I struct { val int; };
      	i0 := new(*I); i0.val = 0;
      	i1 := new(*I); i1.val = 11;
      	i2 := new(*I); i2.val = 222;
    

    bug054.goも同様のv.elemの変更を含みます)

  2. test/fixedbugs/bug045.go:

    --- a/test/fixedbugs/bug045.go
    +++ b/test/fixedbugs/bug045.go
    @@ -13,7 +13,7 @@ type T struct {
     func main() {
      	var ta []*T;
      
    - 	ta = new([1]*T);
    + 	ta = *new(*[1]*T);	// TODO: the first * shouldn\'t be necessary
      	ta[0] = nil;
      }
     /*
    
  3. test/fixedbugs/bug097.go:

    --- a/test/fixedbugs/bug097.go
    +++ b/test/fixedbugs/bug097.go
    @@ -9,9 +9,9 @@ package main
     type A []int;
      
     func main() {
    - 	var a [3]*A;
    + 	var a [3]A;
      	for i := 0; i < 3; i++ {
    - 		a[i] = &A{i};
    + 		a[i] = A{i};
      	}
      	if a[0][0] != 0 { panic(); }
      	if a[1][0] != 1 { panic(); }
    
  4. test/fixedbugs/bug119.go:

    --- a/test/fixedbugs/bug119.go
    +++ b/test/fixedbugs/bug119.go
    @@ -7,14 +7,14 @@
     package main
      
     func foo(a []int) int {
    - 	return (*a)[0]  // this seesm to do the wrong thing
    + 	return a[0]  // this seems to do the wrong thing
      }
      
     func main() {
      	a := &[]int{12};
      	if x := a[0]   ; x != 12 { panicln(1) }
      	if x := (*a)[0]; x != 12 { panicln(2) }
    - 	if x := foo(a) ; x != 12 { panicln(3) }  // fails (x is incorrect)
    + 	if x := foo(*a) ; x != 12 { panicln(3) }  // fails (x is incorrect)
      }
      
     /*
    

コアとなるコードの解説

これらの変更は、Go言語の初期段階における型システム、特に配列、スライス、およびポインタのセマンティクスが進化していたことを明確に示しています。

  • new([10]Element) から new([]Element, 10):

    • これは、固定長配列のポインタを割り当てる初期の試みから、スライスをより適切に扱う方法への移行を示しています。Goではスライスが動的なコレクションの主要な手段であり、この変更はスライスの利用を促進する方向への調整です。現在のGoでは、スライスを初期化する際には通常make([]Element, 0, 10)のようにmakeを使用します。new([]Element, 10)は、[]Element型のゼロ値(nilスライス)へのポインタを返しますが、この文脈ではおそらく、容量10のスライスを意図していたと考えられます。
  • type I struct { val int; }; // BUG: can't be local; の削除とローカル型定義の有効化:

    • これは、Go言語が初期に持っていたローカルスコープでの型定義に関する制約が緩和されたことを示唆しています。これにより、より柔軟なコード記述が可能になりました。
  • ta = new([1]*T); から ta = *new(*[1]*T);:

    • この変更は、Goの型システムがまだ成熟していなかった時期の、ポインタと配列の複雑な相互作用を示しています。new([1]*T)[1]*T型へのポインタを返しますが、*new(*[1]*T)[1]*T型そのものを取得しようとしています。TODOコメントは、この構文が理想的ではないことを開発者自身が認識しており、将来的な改善を期待していたことを示しています。
  • var a [3]*A; から var a [3]A; および a[i] = &A{i}; から a[i] = A{i};:

    • これは、スライスA[]int)をポインタとしてではなく、値として配列に格納するように変更されたことを意味します。Goにおいてスライスは値型であり、代入や関数引数として渡される際にはスライスヘッダ(ポインタ、長さ、容量)がコピーされます。この変更は、スライスの値セマンティクスをより明確に反映し、不必要なポインタの間接参照を避けるためのものです。
  • return (*a)[0] から return a[0] および foo(a) から foo(*a):

    • foo関数が[]int型のスライスを引数に取るにもかかわらず、以前は&[]int{12}というスライスへのポインタを直接渡していました。この修正は、関数が期待する型(スライス)と、実際に渡される型(スライスへのポインタ)の不一致を解消するためのものです。*aとデリファレンスすることで、スライスそのものを関数に渡すようになり、Goの型システムと関数の引数渡し規則に準拠するようになりました。

これらの変更は、Go言語がその設計目標であるシンプルさと効率性を追求する中で、型システムがどのように洗練されていったかを示す貴重な例です。特に、スライスがGoのコレクション型の中核をなすようになる過程で、その初期化と利用方法がより明確かつイディオマティックに定義されていったことが伺えます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(配列、スライス、ポインタ、newmakeに関するセクション)
  • Go言語の初期のコミット履歴と関連する議論(GitHubのコミットページやGoのメーリングリストアーカイブなど)
  • Go言語の設計に関する論文やブログ記事(特に初期の言語設計の決定に関するもの)
  • Go言語の仕様書(各バージョンの仕様変更履歴)
  • Go言語のテストフレームワークとtest/fixedbugsディレクトリの慣習に関する情報