[インデックス 15595] ファイルの概要
このコミットは、Go言語の公式ドキュメントである doc/effective_go.html
の内容を更新するものです。具体的には、スライス(slices)とマップ(maps)に関する説明を改善し、特に「参照型(reference types)」という誤解を招きやすい表現を削除しています。また、Go言語初心者からよく寄せられる質問である2次元配列(2D arrays)の扱い方について、新しいセクションを追加しています。
コミット
commit b3915112b98d21784155f5cafc1e9cb70b33fb7d
Author: Rob Pike <r@golang.org>
Date: Tue Mar 5 14:13:53 2013 -0800
doc/effective_go.html: update slices and maps.
Drop the phrase "reference types", which has caused confusion.
Add a section about 2D arrays, a common newbie question.
R=golang-dev, cespare, adg, rsc
CC=golang-dev
https://golang.org/cl/7423051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b3915112b98d21784155f5cafc1e9cb70b33fb7d
元コミット内容
doc/effective_go.html: update slices and maps. Drop the phrase "reference types", which has caused confusion. Add a section about 2D arrays, a common newbie question.
変更の背景
このコミットの主な背景には、Go言語の学習者がスライス、マップ、チャネルといった組み込み型を理解する上で直面していた混乱があります。特に「参照型」という用語は、C++の参照やJavaの参照型といった他の言語の概念と混同されやすく、Go言語におけるこれらの型の実際の動作(基盤となる配列やデータ構造への参照を保持していること)を正確に伝えていませんでした。Go言語では、これらの型は値渡しされますが、その値が内部的にデータ構造へのポインタを含んでいるため、関数に渡されたスライスやマップの要素を変更すると、呼び出し元にもその変更が反映されるという特性があります。この挙動が「参照型」という言葉で説明されることが多かったのですが、これは厳密には不正確であり、誤解を招く原因となっていました。
また、Go言語の配列とスライスが本質的に1次元であるため、2次元のデータ構造(例えば行列や画像データ)をどのように表現すればよいかという質問が、特に初心者から頻繁に寄せられていました。このコミットは、これらの一般的な疑問や混乱を解消し、Effective Go
ドキュメントの正確性と分かりやすさを向上させることを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。
- 配列 (Arrays): Go言語の配列は、同じ型の要素を固定長で連続して格納するデータ構造です。配列は値型であり、コピーされる際には要素全体がコピーされます。
- スライス (Slices): スライスはGo言語で最も頻繁に使用されるデータ構造の一つで、配列の上に構築された動的なビューです。スライスは、基盤となる配列へのポインタ、長さ(len)、容量(cap)の3つの要素から構成されます。スライス自体は構造体であり、値渡しされますが、その内部のポインタが同じ基盤配列を指すため、スライスを介した変更は共有されます。
- マップ (Maps): マップはキーと値のペアを格納するハッシュテーブルです。キーは一意であり、値にアクセスするために使用されます。マップもスライスと同様に、内部的にデータ構造への参照を保持しており、関数に渡されたマップの変更は呼び出し元に反映されます。
- チャネル (Channels): チャネルはGo言語の並行処理において、ゴルーチン間で値を送受信するためのパイプのようなものです。チャネルも
make
関数で作成され、内部的にデータ構造への参照を保持します。 make
関数とnew
関数:make
はスライス、マップ、チャネルといった組み込みのデータ構造を初期化し、ゼロ値ではない「準備ができた」状態の値を返します。new
は任意の型のゼロ値が格納されたメモリを割り当て、そのメモリへのポインタを返します。
- 空白識別子 (
_
): Go言語では、変数に代入される値を使用しない場合に、その値を破棄するために空白識別子_
を使用します。これは、複数の戻り値を持つ関数で一部の戻り値が不要な場合や、マップの存在チェックで値自体が不要な場合などに利用されます。
技術的詳細
このコミットは、doc/effective_go.html
ドキュメントの複数のセクションにわたって、Go言語のデータ構造に関する説明の正確性を向上させています。
-
「参照型」という用語の削除と再定義:
- スライス、マップ、チャネルの説明から「参照型」という直接的な表現が削除されました。
- 代わりに、「スライスは基盤となる配列への参照を保持する(Slices hold references to an underlying array)」や「マップは基盤となるデータ構造への参照を保持する(maps hold references to an underlying data structure)」といった、より正確な表現に置き換えられています。これにより、これらの型が値渡しされるものの、その値が内部的に共有可能なデータへのポインタを含むというGo言語のセマンティクスが明確になります。
make
関数の説明においても、「これらの3つの型は、内部的には使用前に初期化されなければならないデータ構造への参照を表す(these three types represent, under the covers, references to data structures that must be initialized before use)」と変更され、より厳密な表現になっています。
-
2次元スライスの新しいセクションの追加:
Two-dimensional slices
という新しい<h3>
セクションが追加されました。- Go言語の配列とスライスが1次元であることを明記し、2次元構造を「配列の配列」または「スライスのスライス」として定義する必要があることを説明しています。
type Transform [3][3]float64
(配列の配列) とtype LinesOfText [][]byte
(バイトスライスのスライス) の例が示されています。- 特にスライスのスライスの場合、内部のスライスがそれぞれ異なる長さを持つことができるという柔軟性が強調されています。
- 2次元スライスのメモリ割り当て方法として、以下の2つの主要なパターンが詳細なコード例とともに解説されています。
- 各内部スライスを個別に割り当てる方法: 各行(内部スライス)を独立して
make
で作成します。これは、各行の長さが異なる場合や、後で個々の行が成長・縮小する可能性がある場合に適しています。
// Allocate the top-level slice. picture := make([][]uint8, YSize) // One row per unit of y. // Loop over the rows, allocating the slice for each row. for i := range picture { picture[i] = make([]uint8, XSize) }
- 単一の大きな配列を割り当て、それをスライスして各内部スライスとする方法: 全ての要素を格納する単一の大きなスライスを
make
で作成し、その大きなスライスを部分的にスライスして各行のスライスを生成します。これは、全ての行が同じ長さで、かつ後で成長・縮小しないことが保証される場合に、メモリ割り当ての効率が良い可能性があります。
// Allocate the top-level slice, the same as before. picture := make([][]uint8, YSize) // One row per unit of y. // Allocate one large slice to hold all the pixels. pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8. // Loop over the rows, slicing each row from the front of the remaining pixels slice. for i := range picture { picture[i], pixels = pixels[:XSize], pixels[XSize:] }
- 各内部スライスを個別に割り当てる方法: 各行(内部スライス)を独立して
- どちらの方法を使用するかはアプリケーションの要件に依存し、成長・縮小の可能性や効率性を考慮する必要があることが述べられています。
-
その他の微調整と明確化:
File.Write
のシグネチャに関する説明で、「Write
メソッド」と明示的に記述され、より正確になっています。new
関数とポインタの取得に関する説明で、「new
で割り当てるか、明示的に変数のアドレスを取るか(allocate withnew
or take the address of a variable explicitly)」という選択肢が追加されました。- 配列のイディオムに関する記述で、「スライスを使うべき(Use slices instead)」とより強く推奨する表現に変更されました。
- マップの定義が「異なる型の値を関連付ける」から「ある型(キー)の値を別の型(要素または値)の値と関連付ける」と、より詳細かつ正確に修正されました。
- マップの存在チェックで、値がマップにない場合の「ゼロ値」の例として、文字列の場合に「空文字列(empty string)」と具体的に記述され、より分かりやすくなっています。
- 空白識別子
_
の説明に、「Unixの/dev/null
ファイルに書き込むようなもの(it's a bit like writing to the Unix/dev/null
file)」という比喩が追加され、その役割が直感的に理解しやすくなっています。
これらの変更は、Go言語のドキュメントの正確性と教育的価値を高め、特に初心者にとっての学習障壁を低減することを目的としています。
コアとなるコードの変更箇所
このコミットは、doc/effective_go.html
ファイルのみを変更しています。以下に主要な変更箇所の差分を示します。
--- a/doc/effective_go.html
+++ b/doc/effective_go.html
@@ -831,8 +831,8 @@ case *int:
One of Go\'s unusual features is that functions and methods
can return multiple values. This form can be used to
improve on a couple of clumsy idioms in C programs: in-band
-error returns (such as <code>-1</code> for <code>EOF</code>)
-and modifying an argument.
+error returns such as <code>-1</code> for <code>EOF</code>
+and modifying an argument passed by address.
</p>
<p>
@@ -841,7 +841,8 @@ error code secreted away in a volatile location.
In Go, <code>Write</code>
can return a count <i>and</i> an error: “Yes, you wrote some
bytes but not all of them because you filled the device”.
-The signature of <code>File.Write</code> in package <code>os</code> is:\n+The signature of the <code>Write</code> method on files from
+package <code>os</code> is:
</p>
<pre>
@@ -1208,7 +1209,7 @@ It creates slices, maps, and channels only, and it returns an <em>initialized</e
(not <em>zeroed</em>)
value of type <code>T</code> (not <code>*T</code>).
The reason for the distinction
--is that these three types are, under the covers, references to data structures that
++is that these three types represent, under the covers, references to data structures that
must be initialized before use.
A slice, for example, is a three-item descriptor
containing a pointer to the data (inside an array), the length, and the
@@ -1253,7 +1254,8 @@ v := make([]int, 100)
<p>
Remember that <code>make</code> applies only to maps, slices and channels
and does not return a pointer.
-To obtain an explicit pointer allocate with <code>new</code>.
+To obtain an explicit pointer allocate with <code>new</code> or take the address
+of a variable explicitly.
</p>
<h3 id=\"arrays\">Arrays</h3>
@@ -1300,7 +1302,8 @@ x := Sum(&array) // Note the explicit address-of operator
</pre>
<p>
--But even this style isn\'t idiomatic Go. Slices are.
++But even this style isn\'t idiomatic Go.
++Use slices instead.
</p>
<h3 id=\"slices\">Slices</h3>
@@ -1312,9 +1315,9 @@ dimension such as transformation matrices, most array programming in
Go is done with slices rather than simple arrays.
</p>
<p>
--Slices are <i>reference types</i>, which means that if you assign one
--slice to another, both refer to the same underlying array. For
--instance, if a function takes a slice argument, changes it makes to
-+Slices hold references to an underlying array, and if you assign one
-+slice to another, both refer to the same array.
-+If a function takes a slice argument, changes it makes to
the elements of the slice will be visible to the caller, analogous to
passing a pointer to the underlying array. A <code>Read</code>
function can therefore accept a slice argument rather than a pointer
@@ -1391,19 +1394,87 @@ design, though, we need a little more information, so we\'ll return
to it later.
</p>
+<h3 id=\"two_dimensional_slices\">Two-dimensional slices</h3>
++
+<p>
+Go\'s arrays and slices are one-dimensional.
+To create the equivalent of a 2D array or slice, it is necessary to define an array-of-arrays
+or slice-of-slices, like this:
+</p>
++
+<pre>
+type Transform [3][3]float64 // A 3x3 array, really an array of arrays.
+type LinesOfText [][]byte // A slice of byte slices.
+</pre>
++
+<p>
+Because slices are variable-length, it is possible to have each inner
+slice be a different length.
+That can be a common situation, as in our <code>LinesOfText</code>
+example: each line has an independent length.
+</p>
++
+<pre>
+text := LinesOfText{
+\t[]byte(\"Now is the time\"),
+\t[]byte(\"for all good gophers\"),
+\t[]byte(\"to bring some fun to the party.\"),
+}\n</pre>
++
+<p>
+Sometimes it\'s necessary to allocate a 2D slice, a situation that can arise when
+processing scan lines of pixels, for instance.
+There are two ways to achieve this.
+One is to allocate each slice independently; the other
+is to allocate a single array and point the individual slices into it.
+Which to use depends on your application.
+If the slices might grow or shrink, they should be allocated independently
+to avoid overwriting the next line; if not, it can be more efficient to construct
+the object with a single allocation.
+For reference, here are sketches of the two methods.
+First, a line a time:
+</p>
++
+<pre>
+// Allocate the top-level slice.
+picture := make([][]uint8, YSize) // One row per unit of y.
+// Loop over the rows, allocating the slice for each row.
+for i := range picture {
+\tpicture[i] = make([]uint8, XSize)
+}\n</pre>
++
+<p>
+And now as one allocation, sliced into lines:
+</p>
++
+<pre>
+// Allocate the top-level slice, the same as before.
+picture := make([][]uint8, YSize) // One row per unit of y.
+// Allocate one large slice to hold all the pixels.
+pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
+// Loop over the rows, slicing each row from the front of the remaining pixels slice.
+for i := range picture {
+\tpicture[i], pixels = pixels[:XSize], pixels[XSize:]
+}\n</pre>
+
<h3 id=\"maps\">Maps</h3>
<p>
--Maps are a convenient and powerful built-in data structure to associate
--values of different types.
-+Maps are a convenient and powerful built-in data structure that associate
-+values of one type (the <em>key</em>) with values of another type
-+(the <em>element</em> or <em>value</em>)
The key can be of any type for which the equality operator is defined,\n such as integers,\n floating point and complex numbers,\n strings, pointers, interfaces (as long as the dynamic type\n-supports equality), structs and arrays. Slices cannot be used as map keys,\n+supports equality), structs and arrays.\n+Slices cannot be used as map keys,\n because equality is not defined on them.\n-Like slices, maps are a reference type. If you pass a map to a function\n+Like slices, maps hold references to an underlying data structure.\n+If you pass a map to a function\n that changes the contents of the map, the changes will be visible\n in the caller.\n </p>
@@ -1453,7 +1524,7 @@ if attended[person] { // will be false if person is not in the map
<p>
Sometimes you need to distinguish a missing entry from
a zero value. Is there an entry for <code>\"UTC\"</code>
--or is that zero value because it\'s not in the map at all?
-+or is that the empty string because it\'s not in the map at all?
You can discriminate with a form of multiple assignment.\n </p>
<pre>
@@ -1482,7 +1553,8 @@ func offset(tz string) int {
To test for presence in the map without worrying about the actual value,\n you can use the blank identifier (<code>_</code>).\n The blank identifier can be assigned or declared with any value of any type, with the\n-value discarded harmlessly. For testing just presence in a map, use the blank\n+value discarded harmlessly; it\'s a bit like writing to the Unix <code>/dev/null</code> file.\n+For testing just presence in a map, use the blank\n identifier in place of the usual variable for the value.\n </p>
<pre>
@@ -2463,7 +2535,7 @@ completion. For that, we need channels.\n <h3 id=\"channels\">Channels</h3>
<p>
-Like maps, channels are a reference type and are allocated with <code>make</code>.\n+Like maps, channels are allocated with <code>make</code>.\n If an optional integer parameter is provided, it sets the buffer size for the channel.\n The default is zero, for an unbuffered or synchronous channel.\n </p>
コアとなるコードの解説
このコミットの「コアとなるコードの変更箇所」は、Go言語のドキュメント doc/effective_go.html
のHTMLコンテンツそのものです。Go言語のソースコードやライブラリの動作を変更するものではなく、Go言語の概念を説明するドキュメントの記述を修正・追加しています。
主要な変更点は以下の通りです。
-
「参照型」の表現の修正:
- スライス、マップ、チャネルに関する説明で、これまで使われていた「参照型(reference types)」という表現が削除され、「基盤となるデータ構造への参照を保持する(hold references to an underlying data structure)」という、より正確な表現に置き換えられました。これは、Go言語におけるこれらの型のセマンティクス(値渡しされるが、内部のポインタが共有されるデータ構造を指す)をより正確に反映するためです。この変更は、Go言語の型システムに関する一般的な誤解を解消することを目的としています。
-
2次元スライスのセクションの追加:
Two-dimensional slices
という新しい<h3>
セクションが追加されました。このセクションでは、Go言語の配列とスライスが1次元であるという前提から出発し、2次元のデータ構造をスライスのスライス([][]T
)として表現する方法を詳細に解説しています。- 特に、2次元スライスを割り当てる2つの主要な方法(各内部スライスを個別に割り当てる方法と、単一の大きなスライスを割り当ててからそれをスライスして各行を形成する方法)が、具体的なGoコードの例とともに示されています。これにより、Go言語で2次元データを扱う際のイディオムと、それぞれの方法の利点・欠点が明確に説明されています。これは、Go言語の初心者にとって非常に役立つ情報であり、よくある疑問に直接答えるものです。
これらの変更は、Go言語のドキュメントの品質と正確性を向上させ、特にGo言語の型システムとデータ構造に関する理解を深める上で重要な役割を果たします。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- Effective Go: https://go.dev/doc/effective_go
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- Go Maps in Action: https://go.dev/blog/maps
参考にした情報源リンク
- Go言語の公式ドキュメント
Effective Go
の該当コミットの差分情報 - Go言語の公式ブログ記事(スライス、マップに関するもの)
- Go言語の型システムに関する一般的な解説記事
- Go言語における2次元配列/スライスの実装パターンに関するコミュニティの議論