[インデックス 1727] ファイルの概要
このコミットは、Go言語の仕様書 (doc/go_spec.html
) におけるスライス作成の例を修正し、より明確にするためのものです。特に、make
関数を用いたスライスの作成と、基となる配列を直接操作してスライスを作成する方法との関係性を、具体的なコード例を用いて改善しています。
コミット
commit da34bea950229a2bae01d080cbf833e567c5b84e
Author: Rob Pike <r@golang.org>
Date: Mon Mar 2 20:17:12 2009 -0800
redo poor example of slices.
R=rsc
OCL=25614
CL=25614
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/da34bea950229a2bae01d080cbf833e567c5b84e
元コミット内容
このコミットの元々のメッセージは「redo poor example of slices.」であり、スライスの例が不適切であったため修正するという意図が明確に示されています。
変更の背景
Go言語のスライスは、配列をラップした動的なビューであり、Go言語におけるデータ構造の根幹をなすものです。スライスは、make
関数を使って作成することもできますし、既存の配列からスライスすることもできます。しかし、初期のGo言語のドキュメントでは、これらの概念の関連性や、特にmake
関数が内部的にどのように動作してスライスを生成するのかについての説明が、一部の例で不明瞭であった可能性があります。
このコミットが行われた2009年3月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。この時期は、言語仕様やドキュメントが活発に整備されており、より正確で分かりやすい説明が求められていました。特に、スライスのような重要な概念については、その挙動を正確に伝えることが、将来のGoプログラマーの理解を深める上で不可欠でした。
以前の例 make([]T, capacity)[0 : length]
は、make
が内部的に配列を割り当て、その一部をスライスとして返すという概念を伝えようとしていましたが、この表現自体がやや回りくどく、読者に混乱を与える可能性がありました。このコミットは、その「poor example」を、より直接的で理解しやすい具体的なコード例に置き換えることで、スライスの作成メカニズムを明確にすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。
1. 配列 (Arrays)
Go言語の配列は、同じ型の要素を固定長で連続して格納するデータ構造です。配列の長さは型の一部であり、コンパイル時に決定されます。
例:
var a [5]int // 5つの整数を格納できる配列
2. スライス (Slices)
スライスは、Go言語における可変長シーケンスのデータ型です。スライスは、基となる配列の一部を参照する「ビュー」のようなものです。スライス自体はデータを持たず、以下の3つの要素から構成されます。
- ポインタ (Pointer): スライスが参照する基となる配列の先頭要素へのポインタ。
- 長さ (Length): スライスに含まれる要素の数。
- 容量 (Capacity): スライスの先頭要素から、基となる配列の末尾までの要素の数。
スライスは、配列とは異なり、実行時に長さを変更できます(ただし、容量の範囲内)。
3. make
関数
make
関数は、スライス、マップ、チャネルといった組み込みの参照型を初期化するために使用されます。スライスの場合、make
は指定された型、長さ、およびオプションで容量を持つ基となる配列を内部的に割り当て、その配列を参照するスライスを返します。
構文:
make([]Type, length, capacity)
Type
: スライスの要素の型。length
: スライスの初期の長さ。capacity
(オプション): スライスの基となる配列の容量。指定しない場合、length
と同じになります。
例:
s := make([]int, 5, 10) // 長さ5、容量10のint型スライスを作成
4. new
関数
new
関数は、指定された型のゼロ値に初期化されたメモリを割り当て、その値へのポインタを返します。new
は型Tのポインタ *T
を返します。これは、make
が参照型(スライス、マップ、チャネル)のインスタンスを返すのとは対照的です。
構文:
new(Type)
例:
p := new(int) // int型のゼロ値(0)へのポインタを作成
5. スライス式 (Slice Expressions)
既存の配列やスライスから新しいスライスを作成するために、スライス式を使用します。
構文:
a[low : high]
a[low : high : max]
low
: 新しいスライスの開始インデックス(含まれる)。high
: 新しいスライスの終了インデックス(含まれない)。max
(オプション): 新しいスライスの容量を決定するインデックス。
例:
arr := [10]int{}
s := arr[0:5] // arrの最初の5要素を参照するスライス
技術的詳細
このコミットの技術的な核心は、Go言語の仕様書において、make
関数によるスライスの作成が、内部的に配列の割り当てとスライス操作によって行われることを、より直接的かつ具体的に示す点にあります。
変更前の仕様書では、make([]T, length, capacity)
が「allocating an array and slicing it」と同じスライスを生成すると説明されていました。そして、その例として make([]T, capacity)[0 : length]
というコードが示されていました。この例は、make
が内部的に capacity
の配列を割り当て、その配列の 0
から length
までの範囲をスライスするという概念を表現しようとしています。しかし、この表現は、make
関数自体がスライスを返すという事実と、その内部的なメカニズムを混同させる可能性がありました。特に、make
の結果に対してさらにスライス操作を行うという記述は、読者にとって冗長で理解しにくいものでした。
変更後の仕様書では、この「poor example」を削除し、代わりに以下の2つの具体的なコード例を並べて示しています。
make([]int, 50, 100)
new([100]int)[0:50]
これらの例は、それぞれ異なる方法で「長さ50、容量100のint
型スライス」を作成することを示しています。
-
make([]int, 50, 100)
: これは、Go言語でスライスを作成する際の慣用的な方法です。make
関数が、長さ50、容量100のint
型スライスを直接生成します。内部的には、Goランタイムが100個のint
要素を持つ配列を割り当て、その配列の最初の50要素を指すスライスヘッダを構築します。 -
new([100]int)[0:50]
: これは、make
が内部的に行っている処理を、より低レベルな操作で明示的に表現したものです。new([100]int)
: これは、100個のint
要素を持つ配列をゼロ値で初期化し、その配列へのポインタを返します。[0:50]
: このスライス式は、new([100]int)
が返したポインタが指す配列の、インデックス0から49までの要素を参照する新しいスライスを作成します。この結果、長さは50、容量は100(インデックス0から配列の末尾まで)のスライスが生成されます。
この変更により、読者はmake
関数が提供する高レベルな抽象化と、それが基となる配列とスライス操作によってどのように実現されているのかを、より明確な形で理解できるようになります。特に、new
とスライス式を組み合わせた例は、スライスの「ビュー」としての性質と、基となる配列との密接な関係を直感的に示しています。
この修正は、Go言語の設計思想、特に「シンプルさ」と「明瞭さ」を反映しています。ドキュメントの例は、言語の挙動を正確に、かつ分かりやすく伝えるべきであり、このコミットはその目標を達成するための重要な改善と言えます。
コアとなるコードの変更箇所
変更は doc/go_spec.html
ファイル内で行われています。
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1014,11 +1014,13 @@ make([]T, length, capacity)
</pre>
<p>
-produces the same slice as allocating an array and slicing it:
+produces the same slice as allocating an array and slicing it, so these two examples
+produce the same slice:
</p>
<pre>
-make([]T, capacity)[0 : length]
+make([]int, 50, 100)
+new([100]int)[0:50]
</pre>
具体的には、以下の行が変更されました。
- make([]T, capacity)[0 : length]
(削除)+ make([]int, 50, 100)
(追加)+ new([100]int)[0:50]
(追加)
また、説明文も「produces the same slice as allocating an array and slicing it:」から「produces the same slice as allocating an array and slicing it, so these two examples produce the same slice:」に変更され、複数の例が同じ結果を生むことを強調しています。
コアとなるコードの解説
この変更は、Go言語の仕様書におけるスライスの説明部分を改善しています。
変更前は、make([]T, length, capacity)
が「配列を割り当ててそれをスライスするのと同じスライスを生成する」という説明の後に、make([]T, capacity)[0 : length]
という例が示されていました。この例は、make
関数が内部的に行う処理を表現しようとしていますが、make
の結果に対してさらにスライス操作を行うという記述は、読者にとって混乱を招く可能性がありました。なぜなら、make
関数自体が既にスライスを返すため、その結果をさらにスライスする必要はないからです。これは、make
の内部的な動作を説明するための「概念的な」例としては意図されていたかもしれませんが、実際のコードとしては不自然でした。
変更後は、より具体的で分かりやすい2つの例が並列に示されています。
make([]int, 50, 100)
: これは、Go言語でスライスを作成する際の標準的かつ推奨される方法です。長さ50、容量100のint
型スライスを直接作成します。new([100]int)[0:50]
: これは、make
関数が内部的に行っている処理を、より低レベルなプリミティブ(new
関数による配列の割り当てと、スライス式によるその配列からのスライス作成)を使って明示的に表現したものです。この例は、スライスが基となる配列の「ビュー」であるというGo言語の重要な概念を、非常に明確に示しています。new([100]int)
で100個のint
要素を持つ配列が確保され、その配列の最初の50要素を指すスライスが[0:50]
によって作成されます。このスライスの長さは50、容量は100となります。
この修正により、Go言語の仕様書は、スライスの作成方法とその内部的なメカニズムについて、より正確で理解しやすい情報を提供するようになりました。特に、make
の高レベルな抽象化と、それがどのように低レベルな配列操作に基づいているのかを、具体的なコード例を通じて対比させることで、読者の理解を深める効果があります。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語の仕様書 (現在のバージョン): https://go.dev/ref/spec
- Go Slices: usage and internals: https://go.dev/blog/slices-intro (Goブログの古い記事ですが、スライスの内部構造について詳しく解説されています)
参考にした情報源リンク
- Go言語の公式ドキュメントおよび仕様書
- Go言語のソースコードリポジトリ (GitHub)
- Go言語に関する一般的な知識と概念
- コミットメッセージと差分情報
- Go言語の
make
関数とnew
関数の挙動に関する一般的な理解 - Go言語のスライスに関するブログ記事やチュートリアル(一般的な知識として)