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

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

このコミットは、Go言語の標準ライブラリであるfmtパッケージのテストファイル(src/lib/fmt/fmt_test.go)に対する変更です。具体的には、配列(またはスライス)のフォーマットに関するテストケースが追加されています。

コミット

commit b90b4157d145876649929157381213fd251bdd1a
Author: Rob Pike <r@golang.org>
Date:   Tue Dec 23 09:34:38 2008 -0800

    put array test in table, with TODO to fix when arrays work in interfaces.
    
    TBR=rsc
    OCL=21766
    CL=21766

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

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

元コミット内容

このコミットは、src/lib/fmt/fmt_test.goファイルに以下の変更を加えています。

  1. arrayという名前の[]int型のグローバル変数を定義しました。
    var array = []int{1, 2, 3, 4, 5}
    
  2. fmttestsというテストケースのスライスに、配列のフォーマットに関する新しいエントリを追加しました。
    • %vフォーマット指定子を使用してarray変数を直接フォーマットするテストケースがコメントアウトされた状態で追加されました。この行には、配列がインターフェースで正しく機能するようになったら有効にするというTODOコメントが付記されています。
      // TODO: when arrays work in interfaces, enable this line
      // and delete the TestArrayPrinter routine below
      // FmtTest{ "%v", array, "[1 2 3 4 5]" },
      
    • %vフォーマット指定子を使用してarray変数のポインタ(&array)をフォーマットするテストケースが追加されました。
      FmtTest{ "%v", &array, "&[1 2 3 4 5]" },
      

変更の背景

このコミットは、Go言語の非常に初期の段階(2008年12月、Goが一般公開される約1年前)に行われました。当時のGo言語の型システム、特に配列とインターフェースの間の相互作用には、まだ開発途上の側面がありました。

fmtパッケージは、Goの組み込みのフォーマット機能を提供し、C言語のprintfに似た機能を持っています。このパッケージは、任意の型の値をinterface{}として受け取り、その型に応じて適切な文字列表現を生成します。

このコミットの背景にある問題は、当時のGo言語において、配列型([N]T)が直接インターフェースに渡された際に、その値が期待通りにフォーマットされない、またはインターフェースのセマンティクスと完全に整合しないという挙動があったことです。一方で、配列へのポインタ(*[N]T)は、インターフェースに渡された際に、より予測可能な、または意図されたフォーマット挙動を示していたようです。

このため、開発者は、配列そのものをテストケースに追加するのではなく、一時的に配列へのポインタをテストし、将来的に配列がインターフェースで適切に扱えるようになった際に、コメントアウトされたテストケースを有効にするという計画を立てました。これは、Go言語の型システムが進化していく過程で、特定の型(特に配列のような値型)とインターフェースの間の相互運用性を改善していく必要があったことを示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。

  1. Go言語のfmtパッケージ:

    • fmtパッケージは、GoプログラムでフォーマットされたI/O(入力/出力)を扱うための機能を提供します。最もよく使われる関数はfmt.Printffmt.Sprintffmt.Printなどです。
    • これらの関数は、可変引数としてinterface{}型の値を受け取ります。これにより、任意の型の値をフォーマットすることができます。
    • %vは、Goの値をデフォルトのフォーマットで出力するための「バーブ」(verb)と呼ばれるフォーマット指定子です。構造体や配列、スライスなど、様々な型の値を人間が読める形式で表現する際に便利です。
  2. Go言語の配列とスライス:

    • 配列 (Array): Goの配列は、同じ型の要素を固定長で連続して格納するデータ構造です。配列の長さは型の一部であり、コンパイル時に決定されます。例: [5]intは5つの整数を格納する配列型です。配列は値型であり、関数に渡されるとコピーが作成されます。
    • スライス (Slice): スライスは、配列のセグメント(部分)を参照するデータ構造です。スライスは、基になる配列へのポインタ、長さ、容量の3つの要素で構成されます。スライスは動的な長さを持ち、Goで最も一般的に使われる可変長シーケンスです。[]intのように、角括弧内に長さを指定しない構文はスライスリテラルを作成します。
    • このコミットで定義されているvar array = []int{1, 2, 3, 4, 5}は、厳密にはスライスリテラルであり、arrayはスライス型です。しかし、コミットメッセージが「array test」と明記していること、および当時のGoの初期段階では配列とスライスの内部的な挙動やインターフェースとの関連性が現在とは異なっていた可能性があるため、ここでは「配列」という言葉がより広範な意味で使われていると解釈できます。問題の根源は、スライスが参照する「基になる配列」がインターフェースに渡された際の挙動にあったと考えられます。
  3. Go言語のインターフェース:

    • Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。インターフェースは、特定のメソッドセットを実装する任意の型の値を保持できます。
    • interface{}(空インターフェース)は、メソッドを一切持たないインターフェースであり、Goの任意の型の値を保持できます。これは、型が不明な値を扱う際や、ジェネリックな関数を作成する際に非常に便利です。fmtパッケージの関数は、このinterface{}を利用して様々な型の値を処理します。
    • インターフェースに値を渡す際、値型(配列など)はコピーされ、ポインタ型は参照が渡されます。この挙動が、初期のGoにおける配列とインターフェースの相互作用に影響を与えていた可能性があります。

技術的詳細

このコミットの技術的な核心は、Go言語の初期における配列(およびスライスが参照する基になる配列)と空インターフェース(interface{})の間の相互運用性の課題にあります。

当時のGo言語では、配列型(例: [5]int)の値を直接interface{}型の変数に代入したり、interface{}を引数に取る関数(fmt.Printfなど)に渡したりした場合に、その配列の内部構造がfmtパッケージによって適切に認識され、期待通りの文字列表現(例: [1 2 3 4 5])に変換されない問題があったと考えられます。

一方で、配列へのポインタ(例: *[5]int)をinterface{}に渡すと、fmtパッケージはポインタが指す先の配列の内容を正しく解釈し、フォーマットすることができたようです。これは、インターフェースが値型とポインタ型をどのように扱うかというGoの基本的なセマンティクスに起因します。

  • 値型(配列)をインターフェースに渡す場合: インターフェースは値のコピーを保持します。当時の実装では、このコピーされた配列の値がfmtパッケージの内部的な型ディスパッチメカニズムで正しく処理されなかった可能性があります。例えば、fmtパッケージが配列の要素をイテレートして文字列を構築するロジックを持っていたとしても、インターフェースが保持する配列の「型情報」が不完全であったり、期待されるインターフェース(例: StringerFormatter)を配列型が実装していなかったりしたため、汎用的なフォールバック処理が適用され、期待通りの出力が得られなかったのかもしれません。
  • ポインタ型(配列へのポインタ)をインターフェースに渡す場合: インターフェースはポインタのコピーを保持します。このポインタを通じて、fmtパッケージは基になる配列のデータにアクセスし、その内容を正しくフォーマットできたと考えられます。ポインタは常に参照渡しであるため、fmtパッケージがポインタをデリファレンスして配列の要素にアクセスするパスが、当時の実装でより堅牢であった可能性があります。

コミットのTODOコメント「when arrays work in interfaces」は、この問題がGo言語の設計者によって認識されており、将来的に配列型がインターフェースとよりシームレスに連携できるように改善される予定であったことを明確に示しています。最終的に、Go言語は配列とスライスがインターフェースで適切に機能するように進化し、現在では配列やスライスを直接fmt.Printfなどに渡しても期待通りのフォーマットが得られます。

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

変更はsrc/lib/fmt/fmt_test.goファイルに集中しています。

--- a/src/lib/fmt/fmt_test.go
+++ b/src/lib/fmt/fmt_test.go
@@ -28,6 +28,8 @@ type FmtTest struct {
 
  const B32 uint32 = 1<<32 - 1
  const B64 uint64 = 1<<64 - 1
+var array = []int{1, 2, 3, 4, 5}
+
  
  var fmttests = []FmtTest{
  	// basic string
@@ -77,6 +79,12 @@ var fmttests = []FmtTest{
  	FmtTest{ "% d",		12345,	" 12345" },
  	FmtTest{ "% d",		-12345,	"-12345" },
 
+\t// arrays
+\t// TODO: when arrays work in interfaces, enable this line
+\t// and delete the TestArrayPrinter routine below
+\t// FmtTest{ "%v",		array,			"[1 2 3 4 5]" },
+\tFmtTest{ "%v",		&array,			"&[1 2 3 4 5]" },
+\
  	// old test/fmt_test.go
  	FmtTest{ "%d",		1234,			"1234" },
  	FmtTest{ "%d",		-1234,			"-1234" },

コアとなるコードの解説

  1. var array = []int{1, 2, 3, 4, 5}:

    • この行は、テストで使用するスライス(コミットメッセージでは「配列」と表現)を定義しています。これは、Goの初期段階における配列とスライスの概念の流動性、またはスライスが基になる配列を参照するという事実を反映している可能性があります。
  2. コメントアウトされたテストケース:

    // TODO: when arrays work in interfaces, enable this line
    // and delete the TestArrayPrinter routine below
    // FmtTest{ "%v",		array,			"[1 2 3 4 5]" },
    
    • この部分は、arrayスライス(またはその基になる配列)を直接%vフォーマットで出力した場合の期待値"[1 2 3 4 5]"を示すテストケースです。
    • しかし、前述の通り、当時のGoでは配列がインターフェースで正しく機能しなかったため、このテストはコメントアウトされています。
    • TestArrayPrinterルーチンを削除するという指示は、このテストケースが有効になった際に、一時的な回避策として存在していたであろう別のテストコードが不要になることを示唆しています。
  3. 有効なテストケース:

    FmtTest{ "%v",		&array,			"&[1 2 3 4 5]" },
    
    • この行は、arrayスライスへのポインタ(&array)を%vフォーマットで出力した場合の期待値"&[1 2 3 4 5]"を示すテストケースです。
    • ポインタを渡すことで、当時のGoのfmtパッケージは、ポインタが指す先の配列の内容を正しく解釈し、そのアドレスを示す&記号と配列の文字列表現を結合した形で出力できたことを示しています。

この変更は、Go言語のfmtパッケージが、配列(またはスライス)の値を直接インターフェース経由でフォーマットする際の課題を認識し、その解決に向けて段階的にテストを整備している過程を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式リポジトリのコミット履歴: https://github.com/golang/go
  • Go言語の初期の歴史に関する情報(一般的なGoの歴史に関する記事やブログポスト)
  • Go言語の型システムに関する議論(Goのメーリングリストやフォーラムのアーカイブなど、当時の議論を追うことでより深い理解が得られる可能性がありますが、今回は一般的な知識とコミット内容から推測しました。)
  • Go言語のfmtパッケージのソースコード(現在のバージョンと当時のバージョンを比較することで、どのように進化してきたかを理解できます。)