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

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

このコミットは、Go言語の初期開発段階における重要なバグ修正とテストケースの追加、および既存のテストファイルのクリーンアップを含んでいます。具体的には、配列の要素にnilを不正に代入しようとした際に発生するコンパイルエラーを捕捉するための新しいテストケースbug045.goが追加されました。また、test/golden.outファイルが更新され、この新しいバグテストの出力が反映されています。さらに、test/ken/robfunc.goからは、以前の既知のバグに関するコメントが削除され、コードベースの整理が行われています。

コミット

commit e8010b38e41b45c41e3f0846c02c8c57742cc19f
Author: Rob Pike <r@golang.org>
Date:   Wed Jun 11 10:33:20 2008 -0700

    add bug045: bad nil assigning into array
    
    update robfunc.go (BUG comment deleted)
    
    SVN=122143

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

https://github.com/golang/go/commit/e8010b38e41b45c41e3f0846c02c8c57742cc19f

元コミット内容

このコミットの元の内容は以下の通りです。

  • bug045というバグテストを追加:配列へのnilの不正な代入に関するものです。
  • robfunc.goを更新:BUGコメントが削除されました。
  • SVNリビジョン番号: 122143

変更の背景

この変更は、Go言語のコンパイラが、構造体の配列要素にnilを代入しようとする不正な操作を適切に検出・報告できるようにするために行われました。Go言語では、nilはポインタ、スライス、マップ、チャネル、インターフェースなどのゼロ値を表しますが、非ポインタ型の構造体やプリミティブ型には直接nilを代入することはできません。

コミットメッセージにある「bad nil assigning into array」は、この型システム上の不整合を指しています。Go言語の初期段階では、このような特定の型チェックがまだ完全ではなかった可能性があり、このコミットはそのギャップを埋めるためのものです。test/bugs/bug045.goの追加は、この種の不正なコードがコンパイル時に正しくエラーとして扱われることを保証するための回帰テストとして機能します。

また、test/ken/robfunc.goからのBUGコメントの削除は、おそらく関連するバグが既に修正されたか、あるいはそのコメントがもはや適切でなくなったため、コードベースの整理の一環として行われたと考えられます。

前提知識の解説

Go言語の型システムとnil

Go言語は静的型付け言語であり、変数は宣言時に特定の型を持ちます。nilはGoにおいて特別な意味を持つ識別子で、以下の型のゼロ値(初期値)として使用されます。

  • ポインタ (*T): どの値も指していない状態。
  • スライス ([]T): 基底配列を持たない状態。長さと容量が0。
  • マップ (map[K]V): キーと値のペアを持たない状態。
  • チャネル (chan T): 通信相手を持たない状態。
  • インターフェース (interface{}): どの具象型も保持していない状態。
  • 関数 (func): どの関数も参照していない状態。

重要なのは、nilはこれらの参照型にのみ適用されるということです。Go言語の構造体(struct)は、C言語の構造体やJava/C#のクラスのインスタンスに似ていますが、Goでは値型として扱われることが一般的です。つまり、構造体変数を宣言すると、その構造体のメモリが直接割り当てられ、各フィールドはそれぞれの型のゼロ値で初期化されます。

例えば、type T struct { i int }という構造体がある場合、var t Tと宣言すると、tT型の値であり、t.i0で初期化されます。このt自体はポインタではないため、t = nilのような代入はコンパイルエラーになります。

配列とスライス

Go言語には配列とスライスの2つのデータ構造があります。

  • 配列 ([N]T): 固定長で、要素の型がTであるN個の要素を持つシーケンスです。配列は値型であり、配列変数を別の配列変数に代入すると、要素がコピーされます。
  • スライス ([]T): 可変長で、配列の一部を参照するデータ構造です。スライスは、基底配列へのポインタ、長さ、容量の3つの要素から構成されます。スライスは参照型のように振る舞いますが、厳密にはスライスヘッダが値として渡されます。スライス自体はnilになり得ます(基底配列を参照していない状態)。

このコミットで問題となっているのは、var ta *[]T(スライスへのポインタ)と宣言された変数に、new([1]T)(配列へのポインタ)を代入し、さらにその配列の要素(T型、構造体)にnilを代入しようとしている点です。

技術的詳細

bug045.goのコードは、Go言語の型システムにおけるnilの扱いに関する重要な側面を浮き彫りにしています。

package main

type T struct {
	i int
}

func main() {
	var ta *[]T; // (1) ポインタ to スライス of T
	ta = new([1]T); // (2) ポインタ to 配列 of 1 T
	ta[0] = nil; // (3) 配列の0番目の要素 (T型) に nil を代入
}
  1. var ta *[]T;: ここでtaは「T型のスライスへのポインタ」として宣言されています。つまり、taが指すのはスライスヘッダそのものです。
  2. ta = new([1]T);: new([1]T)は、T型の要素を1つ持つ配列をヒープに割り当て、その配列へのポインタを返します。このポインタの型は*[1]Tです。問題は、ta*[]T型として宣言されているにもかかわらず、*[1]T型の値が代入されている点です。Goのコンパイラは、配列ポインタをスライスポインタに暗黙的に変換することはありません。しかし、初期のGoコンパイラでは、この特定のケースで型チェックが緩かったか、あるいは意図しない変換が行われていた可能性があります。 より正確には、new([1]T)*[1]T型のポインタを返します。Go言語では、配列ポインタ*[N]Tは、その配列の最初の要素へのポインタとして扱われることがあります。この場合、ta*[1]T型として扱われ、ta[0]は配列の0番目の要素、つまりT型の値にアクセスします。
  3. ta[0] = nil;: ここがバグの核心です。ta[0]T型(構造体)の値を参照しています。前述の通り、構造体は値型であり、ポインタ型ではありません。したがって、nilを直接構造体型の変数に代入することはできません。これは型不一致のエラーとなるべきです。

このコミット以前のコンパイラは、このta[0] = nilという不正な代入を適切にエラーとして検出できていなかったか、あるいは誤ったエラーメッセージを出力していた可能性があります。test/golden.outの変更履歴を見ると、以前はfatal error: goc: exit 1のような一般的なエラーメッセージだったものが、fatal error: naddr: const <T>{<i><int32>INT32;}という、より具体的な型に関するエラーメッセージに変わっています。これは、コンパイラがnilT型に変換しようとして失敗したことを示唆しています。

test/golden.outの役割

test/golden.outは、Go言語のテストスイートにおいて、特定のテストケースを実行した際の標準出力やエラー出力の「期待される結果」を記録するファイルです。コンパイラやランタイムの挙動が変更された場合、それに対応してgolden.outも更新されます。

このコミットでは、bug045.goが追加されたことで、そのテストが生成するエラーメッセージがgolden.outに追記されました。また、ken/mfunc.goken/robfunc.goに関する古いBUGコメントやエラーメッセージが削除されており、これはこれらのファイルに関連する問題が解決されたことを示しています。

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

このコミットで直接的にGo言語のコンパイラやランタイムのコアコードが変更されたわけではありません。変更は主にテストコードとテストの期待出力ファイルに集中しています。

  1. test/bugs/bug045.goの新規追加: このファイルは、nilの不正な代入をテストするための新しいGoプログラムです。

    --- /dev/null
    +++ b/test/bugs/bug045.go
    @@ -0,0 +1,21 @@
    +// $G $D/$F.go && $L $F.$A && ./$A.out
    +//
    +// 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 struct {
    +	i int
    +}
    +
    +func main() {
    +	var ta *[]T;
    +
    +	ta = new([1]T);
    +	ta[0] = nil;
    +}
    +/*
    +bug045.go:13: fatal error: goc: exit 1
    +*/
    
  2. test/golden.outの更新: bug045.goのテスト結果が追加され、他の古いBUGコメントやエラー出力が削除されました。

    --- a/test/golden.out
    +++ b/test/golden.out
    @@ -67,8 +67,6 @@ Hello World!
     =========== ken/litfun.go
     
     =========== ken/mfunc.go
    -ken/mfunc.go:13: function call must be single valued (2)
    -BUG: known to fail incorrectly
     
     =========== ken/ptrfun.go
     
    @@ -82,10 +80,6 @@ BUG: known to fail incorrectly
     =========== ken/robfor.go
     
     =========== ken/robfunc.go
    -ken/robfunc.go:74: function call must be single valued (2)
    -ken/robfunc.go:79: function call must be single valued (2)
    -ken/robfunc.go:84: function call must be single valued (2)
    -BUG: known to fail incorrectly
     
     =========== ken/robif.go
     
    @@ -242,6 +236,10 @@ BUG: compilation should succeed
     bugs/bug044.go:23: error in shape across assignment
     BUG: compilation should succeed
     
    +=========== bugs/bug045.go
    +bugs/bug045.go:13: fatal error: naddr: const <T>{<i><int32>INT32;}
    +BUG: known to fail incorrectly
    +
     =========== fixedbugs/bug000.go
     
     =========== fixedbugs/bug005.go
    
  3. test/ken/robfunc.goの変更: 古いBUGコメントが削除されました。これはコードのクリーンアップです。

    --- a/test/ken/robfunc.go
    +++ b/test/ken/robfunc.go
    @@ -54,11 +54,9 @@ func (t *T) m10(a int, b float) int {\n     \n     \n     func f9(a int) (i int, f float) {\n    -// BUG funny return value
     \ti := 9;\n     \tf := float(9);\n     \treturn i, f;\n    -//\treturn;\n     }\n     \n     \n    @@ -83,14 +81,14 @@ func main() {\n     \tr8, s8 = f8(1);\n     \tassertequal(r8, 8, "r8");\n     \tassertequal(int(s8), 8, "s8");\n    -\t\tvar r9 int;\n    -\t\tvar s9 float;\n    -\t\tr9, s9 = f9(1);\n    -\t\tassertequal(r9, 9, "r9");\n    -\t\tassertequal(int(s9), 9, "s9");\n    -\t\tvar t *T = new(T);\n    -\t\tt.x = 1;\n    -\t\tt.y = 2;\n    -\t\tr10 := t.m10(1, 3.0);\n    -\t\tassertequal(r10, 10, "10");\n    +\tvar r9 int;\n    +\tvar s9 float;\n    +\tr9, s9 = f9(1);\n    +\tassertequal(r9, 9, "r9");\n    +\tassertequal(int(s9), 9, "s9");\n    +\tvar t *T = new(T);\n    +\tt.x = 1;\n    +\tt.y = 2;\n    +\tr10 := t.m10(1, 3.0);\n    +\tassertequal(r10, 10, "10");\n     }\n    ```
    
    

コアとなるコードの解説

このコミット自体は、Goコンパイラの内部実装を変更するものではなく、Go言語の型システムが正しく機能していることを検証するためのテストケースを追加しています。

bug045.goの目的は、以下の不正な型変換と代入がコンパイル時にエラーとなることを確認することです。

  1. var ta *[]T; で宣言された*[]T型の変数に、new([1]T)が返す*[1]T型の値を代入しようとしている点。Go言語では、配列ポインタとスライスポインタは異なる型であり、直接的な代入は通常許可されません。しかし、このコードがコンパイルエラーになるのは、次のステップのta[0] = nilが原因です。
  2. ta[0] = nil; の部分で、T型(構造体)の要素にnilを代入しようとしている点。Go言語の型システムでは、nilはポインタ、スライス、マップ、チャネル、インターフェースなどの参照型のゼロ値としてのみ有効です。Tは構造体であり、ポインタ型ではないため、nilを直接代入することはできません。

このテストケースが追加されたことで、Goコンパイラはこのような型不一致のシナリオを確実に捕捉し、開発者に対して適切なエラーメッセージ(fatal error: naddr: const <T>{<i><int32>INT32;})を報告するようになりました。これは、Go言語の型安全性を向上させる上で重要なステップです。

test/golden.outの更新は、この新しいテストケースが期待通りのエラーを生成することを確認するためのものです。また、test/ken/robfunc.goからのBUGコメントの削除は、Go言語の進化に伴い、以前の既知の問題が解決されたことを示しており、コードベースの健全性を保つための定期的なメンテナンスの一環と見なせます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub)
  • Go言語の初期のバグトラッカーやメーリングリストの議論 (公開されている場合)
  • Go言語の型システムに関する一般的な解説記事