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

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

このコミットは、Go言語の仕様書である doc/go_spec.txt に対する重要な更新を含んでいます。主な変更点は、組み込み関数 new()make() の役割の明確化、スライスに関するドキュメントの修正、および len()cap() 関数の説明の改善です。特に、new() がメモリを割り当ててゼロ値を返すのに対し、make() がスライス、マップ、チャネルといった組み込み型を初期化して返すという、Go言語の基本的なメモリ管理と型初期化の概念が明確に区別されました。

コミット

commit 633957bcce8567a6b6f86640810bc74216599405
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Jan 6 13:23:20 2009 -0800

    - documenting old "new()"
    - adding "init()"
    - fixing some bugs with slice documentation
    
    DELTA=118  (45 added, 7 deleted, 66 changed)
    OCL=22084
    CL=22136

注記: コミットメッセージの "adding 'init()'" は、実際の変更内容(make() の追加と説明)と一致していません。これはコミットメッセージの誤記である可能性が高いです。実際の変更は make() 関数の導入と説明の追加です。

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

https://github.com/golang/go/commit/633957bcce8567a6b6f86640810bc74216599405

元コミット内容

doc/go_spec.txt の以下の点が変更されました。

  1. 日付の更新: 仕様書の日付が「(January 5, 2009)」から「(January 6, 2009)」に更新されました。
  2. make() 関数の導入: 組み込み関数リストに make() が追加されました。
  3. スライス、マップ、チャネルの作成方法の変更:
    • スライス、マップ、チャネルの作成に new() ではなく make() を使用するようにドキュメントが修正されました。
    • 例: new([]T, length)make([]T, length) に、new(map[K] V, 100)make(map[K] V, 100) に変更されました。
    • make([]T, length, capacity)new([capacity]T)[0 : length] と実質的に同じであるという説明が追加されました。
  4. スライスドキュメントの修正:
    • スライスの説明が「An (array) slice type」から「A slice type」に修正されました。
    • 「Unlike the capacity, the length of a sub-slice may be larger than the length of the original slice.」という記述が追加されました。(これは現在のGoの仕様とは異なる可能性があり、当時のドラフト段階での記述と考えられます。)
    • スライスの例が []int(1,2,3,4) から [4]int{1, 2, 3, 4}; に変更され、スライス操作の結果が文字列の場合は文字列、配列やスライスの場合はスライスになることが明確化されました。
    • 重要な修正: 「Slices are new arrays (or strings) storing copies of the elements, so changes to the elements of the slice do not affect the original.」という誤った記述が削除されました。これは、Goのスライスが基になる配列のビューであり、コピーではないというGoの重要なセマンティクスを正しく反映するための修正です。
  5. new()make() の役割の明確化:
    • new() 関数は、型 T を受け取り、その型のゼロ値が格納されたメモリを割り当て、そのメモリへのポインタ(*T)を返すことが明確にされました。
    • new() がスライス、マップ、チャネルの初期サイズや容量を指定して割り当てるためのものではないことが強調されました。
    • make() 関数は、スライス、マップ、チャネルといった組み込み型を初期化して返すためのものであると定義されました。
    • new()make() の関係性について、将来的にさらに明確化されるべきであるというTODOコメントが追加されました。
  6. len()cap() 関数の説明の改善:
    • len()cap() がチャネルにも適用されることが明記されました。
    • len()cap() の引数型と結果が表形式で示され、より分かりやすくなりました。
    • 結果の型が常に int であり、int に収まることが保証されるという記述が追加されました。
    • スライスやマップの容量に関する説明が追加されました。
  7. コード例の更新: Sieve() 関数内のチャネル作成が new(chan int) から make(chan int) に変更されました。

変更の背景

このコミットは、Go言語がまだ開発の初期段階(2009年1月)にあった時期に行われたものです。当時のGo言語の仕様書は頻繁に更新されており、言語のセマンティクスや組み込み関数の振る舞いが固まっていく過程にありました。

主な背景としては、以下の点が挙げられます。

  • new()make() の混同の解消: 初期段階のGoでは、new() 関数がメモリの割り当てと初期化の両方に使われることがあり、特にスライス、マップ、チャネルのような参照型の場合、その振る舞いが直感的でない、あるいは誤解を招きやすいという問題がありました。このコミットは、new() を「メモリを割り当ててゼロ値を返す」というシンプルな役割に限定し、make() を「スライス、マップ、チャネルといった組み込み型を適切に初期化して返す」という役割に特化させることで、言語のセマンティクスをより明確にすることを目的としています。
  • スライスのセマンティクスの明確化: Goのスライスは、C++の std::vector のような動的配列とは異なり、基になる配列への「ビュー」または「参照」です。初期のドキュメントには、スライスが要素のコピーを格納するという誤解を招く記述が含まれていました。このコミットは、この誤解を解消し、スライスが基になる配列のセグメントであることを明確にすることで、Goのスライスの強力な特性(効率的な部分配列操作など)を正しく伝えることを目指しました。
  • 仕様書の精度向上: 言語の設計が進むにつれて、仕様書の記述もより正確で網羅的である必要がありました。len()cap() のような基本的な組み込み関数の詳細な振る舞いや、チャネルへの適用範囲を明確にすることは、開発者がGo言語を正しく理解し、効果的に使用するために不可欠でした。

これらの変更は、Go言語の設計思想である「シンプルさ」と「明確さ」を追求する一環として行われました。

前提知識の解説

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

1. Go言語の型システム

Go言語は静的型付け言語であり、すべての変数には型があります。型は、変数が保持できる値の種類と、その値に対して実行できる操作を決定します。

2. 値型と参照型

Goの型は大きく「値型」と「参照型」に分けられます。

  • 値型 (Value Types): 変数に直接値が格納される型です。例: int, float64, bool, string, struct, array。値型を別の変数に代入すると、値がコピーされます。
  • 参照型 (Reference Types): 変数に値そのものではなく、値が格納されているメモリ上のアドレス(ポインタ)が格納される型です。例: slice, map, channel, function, interface。参照型を別の変数に代入すると、同じ基になるデータへの参照が共有されます。

この区別は、new()make() の役割を理解する上で非常に重要です。

3. new() 関数

new() はGoの組み込み関数の一つです。

  • 目的: 型 T の新しい項目を割り当て、その型のゼロ値で初期化されたメモリへのポインタ(*T)を返します。
  • 振る舞い: new(T)*T 型の値を返します。割り当てられたメモリは、その型のゼロ値(数値型なら 0、文字列型なら ""、ブール型なら false、ポインタや参照型なら nil)で初期化されます。
  • 使用例:
    p := new(int)   // p は *int 型で、値は 0
    s := new(string) // s は *string 型で、値は ""
    
    new() は、スライス、マップ、チャネルの初期化には直接使用されません。これらは参照型であり、単にメモリを割り当てるだけでなく、内部構造を適切に初期化する必要があるためです。

4. make() 関数

make() もGoの組み込み関数の一つです。

  • 目的: スライス、マップ、チャネルといった組み込みの参照型を初期化して返します。これらの型は、使用する前に内部データ構造を適切に設定する必要があります。
  • 振る舞い: make(T, args...)T 型の値を返します。Tslice, map, channel のいずれかでなければなりません。引数 args は、それぞれの型に応じて長さ、容量、バッファサイズなどを指定します。
  • 使用例:
    s := make([]int, 5, 10) // int型のスライスを作成。長さ5、容量10
    m := make(map[string]int) // stringキー、int値のマップを作成
    c := make(chan int, 10) // int型のチャネルを作成。バッファサイズ10
    
    make() は、これらの参照型が持つ内部ポインタやヘッダ情報を適切に設定し、すぐに使える状態にします。

5. スライス (Slices)

Goのスライスは、配列のセグメント(部分)を表すデータ構造です。

  • 構造: スライスは、基になる配列へのポインタ、長さ (length)、容量 (capacity) の3つの要素から構成されます。
    • ポインタ: スライスが参照する基になる配列の最初の要素へのポインタ。
    • 長さ (Length): スライスに含まれる要素の数。len() 関数で取得できます。
    • 容量 (Capacity): スライスの基になる配列の、スライスが参照する開始位置から末尾までの要素の最大数。cap() 関数で取得できます。
  • 振る舞い: スライスは基になる配列のビューであるため、複数のスライスが同じ基になる配列を参照することができます。あるスライスを通じて要素を変更すると、同じ基になる配列を参照している他のスライスからもその変更が見えます。これは、このコミットで修正された「スライスがコピーである」という誤解を解消する上で非常に重要です。
  • スライス操作: array[low:high] の形式で、既存の配列やスライスから新しいスライスを作成できます。

6. マップ (Maps)

Goのマップは、キーと値のペアを格納するハッシュテーブルです。

  • 振る舞い: マップは参照型であり、make() を使って初期化する必要があります。nil マップは要素を追加できません。

7. チャネル (Channels)

Goのチャネルは、ゴルーチン間で値を送受信するための通信メカニズムです。

  • 振る舞い: チャネルも参照型であり、make() を使って初期化する必要があります。チャネルはバッファの有無によって同期チャネルと非同期チャネルに分けられます。

8. len()cap() 関数

  • len(v): v の長さ(要素数)を返します。string, array, slice, map, channel に適用できます。
  • cap(v): v の容量を返します。array, slice, channel に適用できます。スライスの場合、基になる配列の残りの容量を示します。チャネルの場合、バッファの容量を示します。

これらの概念は、Go言語のメモリ管理、データ構造、並行処理の基礎を形成しており、このコミットがGo言語の設計においていかに重要な役割を果たしたかを理解する上で不可欠です。

技術的詳細

このコミットの技術的詳細は、Go言語の初期設計における new()make() の役割分担の進化、およびスライスのセマンティクスに関する正確なドキュメントの重要性に集約されます。

new()make() の分離

Go言語の設計者たちは、メモリ割り当てと型の初期化という2つの異なる概念を明確に区別する必要性を認識していました。

  • new(T) の役割: new(T) は、型 T の変数を格納するためのメモリをヒープ上に割り当て、そのメモリを T のゼロ値で初期化し、割り当てられたメモリへのポインタ *T を返します。これは、C++の new T()malloc に似ていますが、Goの new は常にゼロ値を保証するという点で異なります。new は、任意の型に対して使用できますが、主に値型(構造体など)のインスタンスをヒープに割り当てたい場合や、ポインタが必要な場合に使用されます。

  • make(T, args...) の役割: make() は、スライス、マップ、チャネルというGoの組み込み参照型に特化した初期化関数です。これらの型は、単にメモリを割り当てるだけでなく、その内部構造(例えば、スライスのポインタ、長さ、容量、マップのハッシュテーブル、チャネルのバッファなど)を適切に設定する必要があります。make() は、これらの内部構造を初期化し、すぐに使用可能な状態の型 T の値を返します。make() はポインタを返しません。

このコミット以前は、new() がスライス、マップ、チャネルの初期化にも使われていたため、以下のような混乱が生じていました。

// コミット前のGo仕様書での記述例
s := new([]int) // スライスを初期化する意図
c := new(chan int, 10) // バッファ付きチャネルを初期化する意図

しかし、new([]int)*[]int 型の値を返し、そのポインタが指すスライスヘッダはゼロ値(nil ポインタ、長さ0、容量0)で初期化されるだけです。これは、スライスが基になる配列を持つことを意味しません。同様に、new(chan int, 10) のような構文は、new の一般的なセマンティクス(単一のゼロ値の割り当て)と矛盾し、バッファ付きチャネルの作成という特殊な初期化を表現するには不適切でした。

このコミットにより、new() は純粋なメモリ割り当てとゼロ値初期化に特化し、スライス、マップ、チャネルの複雑な初期化は make() に一任されることで、言語のセマンティクスが大幅に明確化されました。これにより、開発者は各関数の目的をより直感的に理解できるようになりました。

スライスのセマンティクス修正

Goのスライスは、その柔軟性と効率性からGo言語の重要な特徴の一つです。しかし、その「ビュー」としての性質は、他の言語の動的配列(例: Pythonのリスト、Javaの ArrayList)とは異なるため、誤解を招きやすい点でもありました。

このコミットで削除された「Slices are new arrays (or strings) storing copies of the elements, so changes to the elements of the slice do not affect the original.」という記述は、Goのスライスの最も根本的な誤解の一つでした。もしスライスが要素のコピーを格納するならば、それは新しい独立したデータ構造となり、基になる配列との関連性は失われます。しかし、Goのスライスは、基になる配列の特定の部分を「参照」する軽量なデータ構造です。

この修正により、Goのスライスが「基になる配列のセグメント(部分)への参照」であるという正しいセマンティクスが明確にされました。これにより、開発者はスライスを介した変更が基になる配列に影響を与え、その基になる配列を参照する他のスライスにも影響を与えることを正しく理解できるようになります。これは、Goにおける効率的なデータ操作、特に大きなデータセットの部分的な処理において極めて重要です。

例:

arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:4] // s1 は {2, 3, 4}
s2 := arr[2:5] // s2 は {3, 4, 5}

s1[0] = 99 // s1 の最初の要素を変更

// arr は {1, 99, 3, 4, 5} になる
// s2 は {3, 4, 5} のままに見えるが、s2[0] は arr[2] を参照しているので、
// s2[0] は 3 のまま。しかし、もし s1[1] を変更したら、それは arr[2] を変更するので、s2[0] も変わる。
// s1[1] = 88
// arr は {1, 99, 88, 4, 5}
// s2 は {88, 4, 5} となる

この挙動は、スライスがコピーではなくビューであることの直接的な結果です。

len()cap() の拡張

len()cap() は、Goのデータ構造のサイズと容量を調べるための基本的な関数です。このコミットでは、これらの関数がチャネルにも適用されることが明確にされました。

  • len(chan): チャネルのバッファに現在格納されている要素の数を返します。
  • cap(chan): チャネルのバッファの最大容量を返します。

これにより、チャネルの現在の状態(バッファの空き状況など)をプログラムで確認できるようになり、より洗練された並行処理のロジックを記述する上で役立ちます。

これらの技術的詳細は、Go言語がその初期段階でいかに厳密に設計され、そのセマンティクスが明確化されていったかを示しています。

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

このコミットは、Go言語の仕様書である doc/go_spec.txt のみを変更しています。Go言語のコンパイラやランタイムのコード自体には変更はありませんが、仕様書の変更は言語の振る舞いに関する公式な定義の変更を意味します。

主な変更箇所は以下の通りです(行番号はコミット時の差分に基づく)。

  • doc/go_spec.txt:
    • L224, L749: 組み込み関数リストに make() を追加。
    • L1478-L1484: スライスの作成に new() ではなく make() を使用するよう変更。make([]T, length, capacity)new([capacity]T)[0 : length] と同等であるという説明を追加。
    • L1536-L1540: マップの作成に new() ではなく make() を使用するよう変更。
    • L1573-L1577: チャネルの作成に new() ではなく make() を使用するよう変更。
    • L1978-L2000: スライスの説明を大幅に修正。特に、スライスが要素のコピーを格納するという誤った記述を削除し、スライス操作の例を更新。
    • L2408-L2419: チャネルの作成例を new() から make() に変更。
    • L3097-L3107: len()cap() の説明を表形式に整理し、チャネルへの適用を追加。
    • L3161-L3199: 「Allocation」セクションを修正し、new() の役割を「型 T のゼロ値が格納されたメモリへのポインタ *T を返す」ことに限定。新たに「Making slices, maps, and channels」セクションを追加し、make() の役割を明確化。new() がスライス、マップ、チャネルの初期化には使われないことを強調。
    • L3274-L3282: Sieve() 関数のコード例で、チャネルの作成を new() から make() に変更。

コアとなるコードの解説

このコミットは、Go言語の仕様書 doc/go_spec.txt のテキスト内容を変更することで、言語のセマンティクスを定義し直しています。したがって、「コアとなるコード」とは、この仕様書の内容そのものを指します。

変更の核心は、new()make() の役割の明確な分離と、スライスの「ビュー」としての性質の正確な記述です。

new()make() の分離に関する変更

以前の仕様では、new() がスライス、マップ、チャネルの初期化にも使われるかのような記述がありましたが、このコミットでそれが修正されました。

変更前(概念):

// new() はメモリを割り当て、ゼロ値で初期化し、ポインタを返す。
// スライス、マップ、チャネルの初期化にも使われることがある。
s := new([]int, length, capacity) // スライス
m := new(map[K]V, capacity)       // マップ
c := new(chan T, bufferSize)      // チャネル

変更後(概念):

// new(T) は型 T のゼロ値が格納されたメモリを割り当て、そのポインタ (*T) を返す。
// スライス、マップ、チャネルの初期化には使われない。
p := new(MyStruct) // 構造体のポインタを割り当て、ゼロ値で初期化

// make(T, args...) はスライス、マップ、チャネルを初期化し、その型の値を返す。
s := make([]int, length, capacity) // スライス
m := make(map[K]V, capacity)       // マップ
c := make(chan T, bufferSize)      // チャネル

この変更は、Go言語のメモリ管理モデルをより一貫性のあるものにし、開発者が newmake のどちらを使うべきかを迷わないようにするためのものです。new は「新しいゼロ値のインスタンスへのポインタ」が必要な場合に、make は「すぐに使える状態の組み込み参照型」が必要な場合に使う、という明確な指針が示されました。

スライスのセマンティクスに関する変更

最も重要な修正は、スライスが基になる配列の「コピー」ではなく「ビュー」であるという点を明確にしたことです。

変更前(誤った記述):

Slices are new arrays (or strings) storing copies of the elements, so
changes to the elements of the slice do not affect the original.

変更後(削除): 上記の記述は削除されました。代わりに、スライスが基になる配列のセグメントであるという理解を促す記述が残されました。

この修正は、Go言語のデータ構造の根本的な理解に影響を与えます。スライスがコピーではなくビューであるという事実は、Goプログラムのパフォーマンス特性(特に大きなデータセットを扱う場合)や、並行処理におけるデータ共有の挙動を理解する上で不可欠です。この変更により、Go言語の設計意図がより正確に伝わるようになりました。

これらの変更は、Go言語の初期の仕様策定において、言語のセマンティクスを厳密に定義し、開発者が誤解なく言語を使用できるようにするための重要なステップでした。

関連リンク

参考にした情報源リンク

(注: 上記の参考リンクは現在のGo言語のドキュメントであり、コミット当時の情報とは異なる可能性がありますが、newmake、スライスのセマンティクスに関する基本的な概念は共通しています。)