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

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

このコミットは、Go言語の初期開発段階において、nil値の扱いに関する既知のバグをテストするために追加されたものです。具体的には、構造体の配列要素にnilを代入しようとした際に発生するコンパイラエラーを捕捉するためのテストケースが導入されています。

コミット

commit a432e09b449d6644adb1abafcbc8a887a7a83d7f
Author: Rob Pike <r@golang.org>
Date:   Fri Jun 13 09:09:22 2008 -0700

    Add nil test, with bug
    
    SVN=122644

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

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

元コミット内容

Add nil test, with bug

SVN=122644

変更の背景

このコミットは、Go言語のコンパイラまたはランタイムにおけるnil値の処理に関する既知のバグを浮き彫りにするために作成されました。特に、構造体の配列の要素にnilを代入しようとした際に、コンパイラが予期せぬエラーを発生させる問題が存在していました。このコミットは、その問題を再現し、将来的な修正のターゲットとするためのテストケース(test/nil.go)を追加しています。test/golden.outの更新は、このテストが「誤って失敗することが既知である」という状態を記録するためのものです。これは、開発初期段階において、特定のバグが認識されており、その修正が保留されていることを示す一般的なプラクティスです。

前提知識の解説

Go言語におけるnil

Go言語においてnilは、ポインタ、インターフェース、マップ、スライス、チャネル、関数といった特定の型の「ゼロ値」を表します。これは、これらの型がまだ有効な値を指していない、または初期化されていない状態を示します。

  • ポインタ (*T): nilポインタは、何も指していない状態です。
  • インターフェース (interface{}): nilインターフェースは、具体的な型も値も持たない状態です。
  • マップ (map[K]V): nilマップは、初期化されていないマップです。要素の追加や削除はできません。
  • スライス ([]T): nilスライスは、基底配列を持たず、長さも容量も0です。
  • チャネル (chan T): nilチャネルは、初期化されていないチャネルです。送受信操作はブロックします。
  • 構造体 (struct): 構造体自体はnilにはなりません。構造体のポインタ型のみがnilになり得ます。

このコミットの時点(2008年)では、Go言語はまだ開発の非常に初期段階にあり、型システムやコンパイラの挙動には多くの未解決の問題やバグが存在していました。特に、nilのセマンティクスは、言語設計の初期段階で頻繁に議論され、調整される領域の一つでした。

Go言語のコンパイラエラー naddrwalktype

Go言語の初期のコンパイラは、内部的にAST(抽象構文木)を走査し、型チェックやコード生成を行っていました。

  • naddr: "node address" の略で、コンパイラがASTノードのアドレスを解決しようとする際に使用される内部的な概念です。このエラーは、コンパイラが特定のノード(この場合はconst <T>{<i><int32>INT32;}という構造体の定数表現)のアドレスを正しく決定できない場合に発生します。これは、型システムまたはコンパイラの内部表現に不整合があることを示唆しています。
  • walktype: コンパイラがASTを走査し、型の情報を処理するフェーズに関連するものです。walktype: switch 1 unknown op SEND l(8)のようなエラーは、コンパイラが予期しない操作(SEND)に遭遇したことを示します。

これらのエラーメッセージは、Goコンパイラの内部実装に関するものであり、当時のコンパイラが特定のコードパターン(特にnilの代入と構造体の組み合わせ)を正しく処理できない状態であったことを示しています。

技術的詳細

このコミットで追加されたtest/nil.goファイルは、Go言語における様々な型の変数にnilを代入するテストを行っています。

package main

type T struct {
	i int
}

type IN interface {
}

func main() {
	var i *int;
	var f *float;
	var s *string;
	var m *map[float] *int;
	var c *chan int;
	var t *T;
	var in IN;
	var ta *[]T; // スライスへのポインタ

	i = nil; // ポインタ
	f = nil; // ポインタ
	s = nil; // ポインタ
	m = nil; // マップ
	c = nil; // チャネル
	t = nil; // 構造体へのポインタ
	in = nil; // インターフェース

	ta = new([1]T); // サイズ1のT型配列へのポインタをtaに代入
	ta[0] = nil;  // BUG (see bugs/bug045.go)
}

このテストの大部分は、Go言語の設計通りにnilを様々なポインタ型、マップ、チャネル、インターフェースに代入できることを確認しています。しかし、問題となるのは以下の行です。

	tta = new([1]T);
	tta[0] = nil;  //BUG (see bugs/bug045.go)

ここでtta*[]T型、つまりT型のスライスへのポインタです。new([1]T)は、サイズ1のT型配列をヒープに割り当て、その配列へのポインタを返します。このポインタはttaに代入されます。

問題の行tta[0] = nil;では、tta[0]T型(構造体)の要素を参照しています。Go言語の仕様では、構造体自体はnilにはなり得ません。nilを代入できるのは、ポインタ、インターフェース、マップ、スライス、チャネル、関数といった特定の参照型のみです。T型は値型であるため、nilを直接代入することはできません。

このコードが実行された際、当時のGoコンパイラはtest/golden.outに記録されているように、以下の致命的なエラーを発生させました。

nil.go:30: fatal error: naddr: const <T>{<i><int32>INT32;}
BUG: known to fail incorrectly

このエラーメッセージは、コンパイラがT型の要素にnilを代入しようとした際に、T型の内部表現({<i><int32>INT32;}T構造体がint型のフィールドiを持つことを示唆)を定数として扱おうとし、そのアドレス解決に失敗したことを示しています。これは、コンパイラがnil代入の型チェックロジックにおいて、値型と参照型の区別を適切に行えていなかった、あるいは構造体のゼロ値表現とnilの間の変換ロジックにバグがあったことを強く示唆しています。

BUG (see bugs/bug045.go)というコメントは、この問題がbug045.goという別のテストファイルで追跡されていることを示しており、このバグが当時認識されていたことを裏付けています。

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

  • test/golden.out:
    • ./nil.goのセクションが追加され、nil.go:30で発生するfatal error: naddr: const <T>{<i><int32>INT32;}というエラーと、「誤って失敗することが既知である」というBUGコメントが追記されました。
  • test/nil.go:
    • 新規ファイルとして追加されました。このファイルは、様々な型の変数にnilを代入するテストケースを含んでいます。特に、構造体の配列要素にnilを代入しようとする行(tta[0] = nil;)が、このコミットが意図するバグの再現箇所です。

コアとなるコードの解説

test/golden.outの変更

test/golden.outは、Go言語のテストスイートにおいて、特定のテストが期待する出力(エラーメッセージや成功メッセージなど)を記録するためのファイルです。このファイルにnil.goのエラー出力が追加されたことは、nil.goが意図的に失敗するテストであり、その失敗が特定のコンパイラエラー(naddrエラー)であることを示しています。BUG: known to fail incorrectlyという記述は、このテストが示す問題がバグとして認識されており、まだ修正されていないことを明示しています。

test/nil.goの新規追加

このファイルは、Go言語におけるnilの代入規則をテストするために設計されています。

  1. 様々な型へのnil代入: *int, *float, *string (ポインタ型), *map[float] *int (マップへのポインタ), *chan int (チャネルへのポインタ), *T (構造体へのポインタ), IN (インターフェース) といった多様な型に対してnilを代入しています。これらはすべてGo言語の仕様上、nilを代入できる型です。

  2. 問題の箇所:

    type T struct {
    	i int
    }
    // ...
    func main() {
    	// ...
    	var ta *[]T; // T型スライスへのポインタ
    	ta = new([1]T); // サイズ1のT型配列を割り当て、そのポインタをtaに代入
    	ta[0] = nil;  // BUG (see bugs/bug045.go)
    }
    

    ここでta*[]T型であり、new([1]T)によって[1]T型の配列がヒープに確保され、そのポインタがtaに代入されます。したがって、ta[0]T型の要素(構造体)を参照します。Go言語では、構造体は値型であり、nilを直接代入することはできません。この代入は型エラーとなるべきですが、当時のコンパイラはこれをnaddrという内部エラーとして処理していました。これは、コンパイラがnilを構造体要素に代入しようとした際に、その構造体の内部表現を定数として扱い、そのアドレス解決に失敗したことを示しています。

このテストケースは、Go言語の初期のコンパイラが、特定の型(特に構造体)とnilの組み合わせにおいて、型チェックや内部処理に不備があったことを明確に示しています。このバグは、後のGo言語のバージョンで修正され、現在ではこのような代入はコンパイルエラーとなります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go.dev/ref/spec)
  • Go言語のソースコードリポジトリ (github.com/golang/go)
  • Go言語の初期のコミット履歴とテストファイルの内容分析
  • Go言語のコンパイラに関する一般的な知識
  • naddrwalktypeといったGoコンパイラの内部エラーに関する一般的な情報(Go言語のコンパイラ開発に関する議論や資料)