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

[インデックス 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つの具体的なコード例を並べて示しています。

  1. make([]int, 50, 100)
  2. 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つの例が並列に示されています。

  1. make([]int, 50, 100): これは、Go言語でスライスを作成する際の標準的かつ推奨される方法です。長さ50、容量100のint型スライスを直接作成します。
  2. new([100]int)[0:50]: これは、make 関数が内部的に行っている処理を、より低レベルなプリミティブ(new 関数による配列の割り当てと、スライス式によるその配列からのスライス作成)を使って明示的に表現したものです。この例は、スライスが基となる配列の「ビュー」であるというGo言語の重要な概念を、非常に明確に示しています。new([100]int) で100個のint要素を持つ配列が確保され、その配列の最初の50要素を指すスライスが [0:50] によって作成されます。このスライスの長さは50、容量は100となります。

この修正により、Go言語の仕様書は、スライスの作成方法とその内部的なメカニズムについて、より正確で理解しやすい情報を提供するようになりました。特に、make の高レベルな抽象化と、それがどのように低レベルな配列操作に基づいているのかを、具体的なコード例を通じて対比させることで、読者の理解を深める効果があります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントおよび仕様書
  • Go言語のソースコードリポジトリ (GitHub)
  • Go言語に関する一般的な知識と概念
  • コミットメッセージと差分情報
  • Go言語のmake関数とnew関数の挙動に関する一般的な理解
  • Go言語のスライスに関するブログ記事やチュートリアル(一般的な知識として)