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

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

このコミットは、Go言語の初期開発段階におけるテストコードの修正を目的としています。具体的には、配列の初期化とポインタの扱い、および型変換の構文に関する複数のテストケースを修正し、当時のGo言語のセマンティクスに合致させる変更が加えられています。コミットメッセージによると、この修正後も complit, hilbert, initcomma の3つのテストはまだ失敗している状態でした。

コミット

commit 61a7e44002949a5e335dc7cfc7c9167074c3487a
Author: Rob Pike <r@golang.org>
Date:   Sat Dec 20 13:38:29 2008 -0800

    fix some tests. only 3 remain broken (complit, hilbert, initcomma).
    leaving golden.out alone for now.
    
    R=ken
    DELTA=13  (0 added, 0 deleted, 13 changed)
    OCL=21682
    CL=21682

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

https://github.com/golang/go/commit/61a7e44002949a5e335dc7cfc7c9167074c3487a

元コミット内容

このコミットは、Go言語のテストスイートにおける複数の既存のテストファイルを修正するものです。主な目的は、当時のGo言語のコンパイラやランタイムの挙動に合わせて、テストが正しくパスするようにすることでした。特に、配列の初期化方法と型変換の構文に関する変更が中心となっています。

変更の背景

このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の、活発な開発初期段階にありました。言語の仕様や構文は頻繁に変更されており、それに伴い既存のコードベース(特にテストコード)も更新する必要がありました。

このコミットの背景には、以下の主要な言語仕様の変更または明確化があったと考えられます。

  1. new 組み込み関数のセマンティクス: new は常に指定された型のゼロ値へのポインタを返します。初期のGo言語では、new(*[N]Type) のように書かれることがあったようですが、これは「[N]Type 型へのポインタ」へのポインタを意味し、意図しない二重ポインタになっていました。正しくは「[N]Type 型のゼロ値へのポインタ」を得るために new([N]Type) と書くべきでした。このコミットは、この誤用を修正しています。
  2. 型変換構文の統一: 初期には convert(Type, expr) のような組み込み関数による型変換が使われていたようですが、このコミットでは Type(expr) という、より簡潔で一般的な型キャスト構文に移行しています。これは、Go言語がC言語や他の現代的な言語の慣習を取り入れ、より一貫性のある構文を目指していたことを示唆しています。また、インターフェースの型アサーションも interfaceValue.(Type) という現在の構文に統一されています。
  3. ポインタの明示的なデリファレンス: 配列へのポインタを扱う際に、配列の内容にアクセスしたり、スライスを作成したりする場合には、ポインタを明示的にデリファレンス (*pointer) する必要があるというセマンティクスが明確化されたか、あるいはその適用が厳格化されたと考えられます。

これらの変更は、Go言語の設計が固まりつつあった時期に、言語の使いやすさ、一貫性、そしてパフォーマンスを向上させるための重要なステップでした。

前提知識の解説

このコミットの変更内容を理解するためには、Go言語の基本的な概念と、特に初期のGo言語における特徴的な構文について知っておく必要があります。

  • ポインタ (Pointers): Go言語におけるポインタは、変数のメモリアドレスを指し示します。*TypeType 型の値へのポインタを表し、&variablevariable のアドレスを取得します。ポインタが指す値にアクセスするには、*pointer のようにデリファレンス(間接参照)します。
  • new 組み込み関数: new(Type) は、Type 型の新しいゼロ値のメモリを割り当て、その値へのポインタを返します。例えば、new(int)*int 型の値を返します。
  • 配列 (Arrays): Go言語の配列は、固定長の要素のシーケンスです。[N]Type のように宣言され、N は配列のサイズです。配列は値型であり、変数に代入されるとコピーされます。
  • スライス (Slices): スライスは配列のセグメントを参照する動的なデータ構造です。[]Type のように宣言され、基になる配列の一部を「ビュー」として提供します。スライスはポインタ、長さ、容量の3つの要素から構成されます。
  • 型変換 (Type Conversion): ある型の値を別の型に変換することです。Go言語では、明示的な型変換が必要です。
  • 型アサーション (Type Assertion): インターフェース型の値が、特定の具象型を保持しているかどうかをチェックし、その具象型の値を取り出すための構文です。interfaceValue.(Type) の形式で記述されます。
  • Go言語の初期開発: Go言語は2007年にGoogleで開発が始まり、2009年11月にオープンソースとして公開されました。このコミットが行われた2008年12月は、公開前の活発な開発期間であり、言語仕様が流動的でした。

技術的詳細

このコミットにおける技術的な変更は、Go言語の初期の設計思想と、それがどのように進化していったかを反映しています。

  1. new(*[N]Type) から new([N]Type) への変更:

    • 元のコード new(*[10]Element) は、[10]Element 型へのポインタ (*[10]Element) を指すポインタ (**[10]Element) を割り当てようとしていました。これは通常、意図しない二重ポインタの作成につながります。
    • 修正後の new([10]Element) は、[10]Element 型の配列を割り当て、その配列へのポインタ (*[10]Element) を返します。これが new を使って配列を初期化する際の正しいGoのイディオムです。
    • この変更は、new が常に「指定された型のゼロ値へのポインタ」を返すという原則を厳密に適用した結果です。
  2. convert(Type, expr) から Type(expr) への変更:

    • 初期のGo言語には、convert という組み込み関数が存在し、型変換のために使用されていました。これは、Pythonなどの一部の言語に見られるような関数形式の型変換に似ています。
    • このコミットでは、convert(int, input[inputindex]) のような記述が int(input[inputindex]) に変更されています。これは、C言語やJava、JavaScriptなど、多くのプログラミング言語で採用されている Type(expression) 形式の型キャスト構文への移行を示しています。
    • この変更は、言語の構文をより簡潔にし、他の言語からの学習者がGoに慣れやすくするための改善と考えられます。また、convert 関数が持つ可能性のある曖昧さ(例えば、型変換とデータ変換の区別)を排除し、純粋な型キャストに特化させる意図があったかもしれません。
  3. ポインタの明示的なデリファレンス (*a):

    • test/ken/array.gotest/ken/string.go の変更は、配列へのポインタを扱う際の厳密なデリファレンス要件を強調しています。
    • 例えば、a := new(*[40]int) の場合、a*[40]int 型へのポインタです。setpdsumpd のような関数が配列自体を引数として期待する場合、*a とデリファレンスして配列の値を渡す必要があります。
    • 同様に、スライスを作成する際も、b := (*a)[5:30] のように、まずポインタをデリファレンスして基になる配列にアクセスし、その上でスライス操作を行う必要があります。
    • これは、Go言語がポインタと値のセマンティクスを明確に区別していることの表れであり、コンパイラがより厳密な型チェックを行うようになった結果とも考えられます。
  4. インターフェースの型アサーション (v.At(i).(*S)):

    • test/vectors.go の変更は、インターフェースの型アサーションに関するものです。
    • convert(*S, v.At(i)) は、v.At(i) が返すインターフェース値から *S 型の値を取り出そうとしていました。
    • 修正後の v.At(i).(*S) は、Go言語における標準的な型アサーションの構文です。これは、インターフェース値が実際に *S 型の具象値を保持している場合に、その値と true を返し、そうでなければパニックを起こすか、第二の戻り値で false を返します。
    • この変更も、言語の構文を統一し、よりGoらしいイディオムに準拠させるためのものです。

これらの変更は、Go言語がその初期段階で、より堅牢で、予測可能で、そしてイディオム的なプログラミングスタイルを確立しようとしていたことを明確に示しています。

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

このコミットにおけるコアとなるコードの変更は、主に以下のパターンに集約されます。

  1. 配列の new 呼び出しの修正:

    - v.elem = new(*[10]Element);
    + v.elem = new([10]Element);
    

    test/fixedbugs/bug027.go, test/fixedbugs/bug045.go, test/fixedbugs/bug054.go, test/fixedbugs/bug059.go など)

  2. convert 組み込み関数の削除と型キャストへの置き換え:

    - c = convert(int, input[inputindex]);
    + c = int(input[inputindex]);
    
    - tokenbuf[i] = convert(byte, c);
    + tokenbuf[i] = byte(c);
    
    - v = 10 * v + convert(int, tokenbuf[i] - '0');
    + v = 10 * v + int(tokenbuf[i] - '0');
    

    test/ken/rob2.go

  3. 配列ポインタの明示的なデリファレンス:

    - setpd(a);
    + setpd(*a);
    
    - b := a[5:30];
    + b := (*a)[5:30];
    

    test/ken/array.go

    - c = string(z2);
    + c = string(*z2);
    

    test/ken/string.go

  4. インターフェースの型アサーション構文の修正:

    - x := convert(*S, v.At(i));
    + x := v.At(i).(*S);
    

    test/vectors.go

  5. 単純なロジックバグ修正:

    - if len(c) != 1 { panicln("len a", len(a)) }
    + if len(c) != 1 { panicln("len a", len(c)) }
    

    test/initcomma.go

コアとなるコードの解説

上記の変更箇所は、Go言語の初期の設計における重要な進化を示しています。

  • new と配列: new([10]Element) は、[10]Element 型の配列をヒープに割り当て、その配列へのポインタを返します。これにより、v.elem は正しく配列へのポインタを保持するようになります。元の new(*[10]Element) は、配列へのポインタへのポインタを生成してしまい、その後のアクセスが複雑になるか、コンパイルエラーやランタイムエラーを引き起こす可能性がありました。この修正は、new のセマンティクスを明確にし、Go言語におけるメモリ割り当てのイディオムを確立する上で重要でした。

  • convert から Type() キャストへ: convert 関数は、Go言語の初期段階で型変換のために使用されていましたが、このコミットで Type(expr) という現在の型キャスト構文に置き換えられました。これは、Go言語がより簡潔で、他のC系言語に慣れた開発者にとって直感的な構文を採用したことを意味します。int(input[inputindex]) のように、型名を関数のように使用することで、input[inputindex] の値を int 型に変換しています。この変更は、言語の構文を統一し、学習コストを削減する上で大きな意味を持ちました。

  • 配列ポインタのデリファレンス: setpd(*a)(*a)[5:30] のように、* 演算子を使ってポインタ a が指す配列自体を明示的にデリファレンスしています。Go言語では、ポインタと値の区別が厳密であり、関数が値型の引数を期待する場合や、配列の要素にアクセスしたりスライスを作成したりする場合には、ポインタが指す実際の値(この場合は配列)を取り出す必要があります。この修正は、Go言語の型システムとポインタのセマンティクスに対する理解が深まり、より厳密なコード記述が求められるようになったことを示唆しています。

  • インターフェースの型アサーション: v.At(i).(*S) は、インターフェース値 v.At(i) が実際に *S 型の具象値を保持していることを確認し、その値を x に代入するGo言語の標準的な構文です。これは、convert 関数が持つ汎用的な型変換の役割から、インターフェースの特定の具象型への変換という、より特化した操作を明確に表現するための変更です。これにより、コードの意図がより明確になり、型安全性が向上します。

これらの変更は、Go言語がその初期段階で、言語の設計原則(シンプルさ、明示性、安全性)を具体化し、現在のGo言語の姿へと進化していく過程の重要な一歩でした。

関連リンク

参考にした情報源リンク