[インデックス 1428] ファイルの概要
このコミットは、Go言語の初期の仕様書である doc/go_spec.txt
に対する変更を記録しています。具体的には、Go言語における複合リテラル(Composite Literals)の定義と使用方法が調整され、構造体(struct)、配列(array)、スライス(slice)、マップ(map)のリテラルが明確に導入されました。これは、Go言語の設計が初期段階でどのように進化し、より直感的で強力な言語機能がどのように形成されていったかを示す重要な一歩です。
コミット
commit 91bbd6484b9d322c4b0620ed55365ed9924338b7
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jan 7 09:31:35 2009 -0800
Adjusted language for literals:
- now have struct, array, slice, and map literals
DELTA=34 (13 added, 6 deleted, 15 changed)
OCL=22180
CL=22204
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/91bbd6484b9d322c4b0620ed55365ed9924338b7
元コミット内容
このコミットの元の内容は、Go言語の仕様書におけるリテラルに関する記述の調整です。特に、構造体、配列、スライス、マップの各型に対するリテラル表現が導入されたことが明記されています。これは、Go言語の初期設計において、これらの複合データ構造を初期化するためのより簡潔で一貫性のある方法を提供することを目的としています。
変更の背景
この変更の背景には、Go言語の初期開発における言語設計の洗練があります。コミットメッセージと差分から読み取れるように、以前の仕様では new
キーワードのセマンティクスが「奇妙」であると認識されており、スライス、マップ、チャネル(channels)のリテラルを導入することで、この問題を解決しようとしていました。
Go言語の初期には、メモリ割り当てと初期化に関して new
と make
という2つの組み込み関数が存在しました。new
は型のゼロ値を割り当て、そのポインタを返しますが、これは複合型(特にスライス、マップ、チャネル)の初期化には不十分でした。これらの型は、単にメモリを割り当てるだけでなく、内部構造(例えば、スライスの長さと容量、マップのハッシュテーブル)を適切に初期化する必要があるためです。
このコミットは、new
のセマンティクスを単純化し、複合リテラルを導入することで、開発者がこれらのデータ構造をより直感的に、かつ宣言的に初期化できるようにすることを目指しました。これにより、コードの可読性と記述性が向上し、Go言語の「シンプルさ」という設計哲学に合致するようになりました。また、配列の代入に関する曖昧さも解消され、配列の代入が許可されるようになりました。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念と初期の設計に関する知識が必要です。
- リテラル(Literals): プログラム内で直接値を表現する方法。例えば、
123
は整数リテラル、"hello"
は文字列リテラルです。複合リテラルは、構造体、配列、スライス、マップなどの複合データ型を直接初期化するための構文です。 - 構造体(Structs): 異なる型のフィールドをまとめた複合データ型。
- 配列(Arrays): 同じ型の要素を固定長で並べたシーケンス。
- スライス(Slices): 配列の一部を参照する動的なビュー。長さと容量を持ち、Go言語で最も一般的に使用されるシーケンス型です。
- マップ(Maps): キーと値のペアを格納するハッシュテーブル。
new
とmake
: Go言語におけるメモリ割り当てと初期化のための組み込み関数。new(T)
: 型T
のゼロ値を割り当て、その型へのポインタ*T
を返します。メモリを割り当てるだけで、値の初期化は行いません(数値型は0、文字列は空文字列、ポインタはnilなど)。make(T, args)
: スライス、マップ、チャネルといった組み込みの参照型を初期化するために使用されます。これらの型は、単にメモリを割り当てるだけでなく、内部データ構造を適切に設定する必要があります。
- Go言語の仕様書(Go Programming Language Specification): Go言語の構文とセマンティクスを定義する公式文書。言語の挙動に関する最終的な権威となります。
このコミット以前は、スライスやマップの初期化に new
を使うことが混乱を招く可能性があり、より宣言的な方法が求められていました。複合リテラルの導入は、この問題に対する直接的な解決策でした。
技術的詳細
このコミットの技術的詳細は、doc/go_spec.txt
の「Composite Literals」セクションの変更に集約されています。
変更点:
-
LiteralType
の定義の変更:- 変更前:
LiteralType = TypeName | ArrayType | MapType | StructType .
- 変更後:
LiteralType = Type | "..." ElementType .
- この変更により、
LiteralType
がより一般的なType
を受け入れるようになり、さらに配列リテラルで[...]
構文を使用して要素数から配列の長さを推論できるようになりました。
- 変更前:
-
複合リテラルの適用範囲の明確化:
- 変更前は「If LiteralType is a TypeName, the denoted type must be an array, map, or structure.」と記述されていましたが、変更後は「The LiteralType must be an struct, array, slice, or map type.」と明確に記述され、スライスが複合リテラルの対象として明示的に追加されました。
-
配列リテラルの
...
構文の導入:- 配列リテラルにおいて、長さの指定の代わりに
...
を使用することで、提供された要素の数に基づいて配列の長さを自動的に決定できるようになりました。 - 例:
days := [...]string{"sat", "sun"};
はlen(days) == 2
となります。
- 配列リテラルにおいて、長さの指定の代わりに
-
スライスリテラルの導入と説明:
- スライスリテラルが導入され、そのセマンティクスが詳細に説明されました。スライスリテラルは、基となる配列リテラル全体を記述するスライスであり、その長さと容量はリテラルで提供される要素の数に等しいと定義されました。
[]T{x1, x2, ... xn}
の形式のスライスリテラルは、実質的に[n]T{x1, x2, ... xn}[0 : n]
という配列リテラルにスライス操作を適用したもののショートカットであると説明されています。
-
TODO
およびClosed
セクションの更新:- 以前の
TODO
項目であった「new as it is now is weird - need to go back to previous semantics and introduce literals for slices, maps, channels」がClosed
に移動され、「done」とマークされました。これは、このコミットがその課題を解決したことを示しています。 - 「determine if really necessary to disallow array assignment」という項目も
Closed
に移動され、「allow array assignment」とマークされました。これにより、配列の代入が許可されるようになったことが示唆されます。
- 以前の
これらの変更は、Go言語の型システムと初期化メカニズムをより一貫性のあるものにし、開発者が複合データ構造を扱う際の利便性を大幅に向上させました。特にスライスリテラルの導入は、Go言語で最も頻繁に使用されるデータ構造の一つであるスライスの初期化を簡潔に行う上で不可欠な要素となりました。
コアとなるコードの変更箇所
このコミットで変更されたコアとなるコードは、Go言語の仕様書である doc/go_spec.txt
の以下のセクションです。
--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -3,7 +3,7 @@ The Go Programming Language Specification (DRAFT)
Robert Griesemer, Rob Pike, Ken Thompson
-(January 6, 2009)
+(January 7, 2009)
----
@@ -45,9 +45,6 @@ Todo's:
doesn't correspond to the implementation. The spec is wrong when it
comes to the first index i: it should allow (at least) the range 0 <= i <= len(a).
also: document different semantics for strings and arrays (strings cannot be grown).
-[ ] new as it is now is weird - need to go back to previous semantics and introduce
- literals for slices, maps, channels
-[ ] determine if really necessary to disallow array assignment
Open issues:
@@ -95,6 +92,9 @@ Decisions in need of integration into the doc:
Closed:
+[x] new as it is now is weird - need to go back to previous semantics and introduce
+ literals for slices, maps, channels - done
+[x] determine if really necessary to disallow array assignment - allow array assignment
[x] semantics of statements - we just need to fill in the language, the semantics is mostly clear
[x] range statement: to be defined more reasonably
[x] need to be specific on (unsigned) integer operations: one must be able
@@ -1774,43 +1774,50 @@ Composite Literals
----
Literals for composite data structures consist of the type of the value
-followed by a braced expression list for array and structure literals,
+followed by a braced expression list for array, slice, and structure literals,
or a list of expression pairs for map literals.
CompositeLit = LiteralType "{" [ ( ExpressionList | ExprPairList ) [ "," ] ] "}" .
-\tLiteralType = TypeName | ArrayType | MapType | StructType .
+\tLiteralType = Type | "..." ElementType .
ExprPairList = ExprPair { "," ExprPair } .
ExprPair = Expression ":" Expression .
-If LiteralType is a TypeName, the denoted type must be an array, map, or
-structure. The types of the expressions must match the respective key, element,
-and field types of the literal type; there is no automatic type conversion.
+The LiteralType must be an struct, array, slice, or map type.
+The types of the expressions must match the respective field, element, and
+key types of the LiteralType; there is no automatic type conversion.
Composite literals are values of the type specified by LiteralType; that is
a new value is created every time the literal is evaluated. To get
a pointer to the literal, the address operator "&" must be used.
Given
-\ttype Rat struct { num, den int };
-\ttype Num struct { r Rat; f float; s string };
+\ttype Rat struct { num, den int }
+\ttype Num struct { r Rat; f float; s string }
one can write
pi := Num{Rat{22, 7}, 3.14159, "pi"};
-\n-TODO section below needs to be brought into agreement with 6g.\n-\n The length of an array literal is the length specified in the LiteralType.\n If fewer elements than the length are provided in the literal, the missing\n elements are set to the appropriate zero value for the array element type.\n-It is an error to provide more elements than specified in LiteralType.\n-If no length is specified, the length is the number of elements provided\n-in the literal.\n+It is an error to provide more elements than specified in LiteralType. The\n+notation "..." may be used in place of the length expression to denote a\n+length equal to the number of elements in the literal.\n+\n+\tbuffer := [10]string{}; // len(buffer) == 10\n+\tprimes := [6]int{2, 3, 5, 7, 9, 11}; // len(primes) == 6\n+\tdays := [...]string{"sat", "sun"}; // len(days) == 2\n+\n+A slice literal is a slice describing the entire underlying array literal.\n+Thus, the length and capacity of a slice literal is the number of elements\n+provided in the literal. A slice literal of the form\n+\n+\t[]T{x1, x2, ... xn}\n+\n+is essentially a shortcut for a slice operation applied to an array literal:\n \n-\tbuffer := [10]string{}; // len(buffer) == 10\n-\tprimes := &[6]int{2, 3, 5, 7, 9, 11}; // len(primes) == 6\n-\tweekenddays := &[]string{"sat", "sun"}; // len(weekenddays) == 2
+\t[n]T{x1, x2, ... xn}[0 : n]\n
Map literals are similar except the elements of the expression list are\n key-value pairs separated by a colon:\n```
## コアとなるコードの解説
上記の差分は、Go言語の複合リテラルに関する仕様を根本的に変更し、より柔軟で強力な初期化構文を導入しています。
1. **`LiteralType` の拡張**:
* 以前は `TypeName`(既存の型名)か、`ArrayType`、`MapType`、`StructType` のいずれかとして定義されていました。
* 変更後は `Type`(任意の型)または `[...]ElementType` となりました。これにより、型名だけでなく、直接型定義(例: `struct { ... }`)もリテラルタイプとして使用できるようになり、匿名構造体などのリテラル表現が可能になりました。
* `[...]ElementType` は、配列リテラルにおいて要素数から長さを推論する新しい構文を導入しています。
2. **複合リテラルの対象型の明確化**:
* 以前は「`LiteralType` が `TypeName` の場合、その型は配列、マップ、または構造体でなければならない」という記述でした。
* 変更後は「`LiteralType` は構造体、配列、スライス、またはマップ型でなければならない」と明確に記述され、スライスが複合リテラルの主要な対象として明示的に追加されました。これは、スライスがGo言語で非常に頻繁に使用されるデータ構造であるため、その初期化を簡潔に行うための重要な変更です。
3. **配列リテラルの `...` 構文の詳細化**:
* 配列リテラルの長さ指定において、`...` を使用することで、提供される要素の数に基づいて配列の長さを自動的に決定できることが明記されました。これにより、要素の追加や削除があった場合に、手動で長さを更新する必要がなくなります。
4. **スライスリテラルの導入とセマンティクス**:
* この変更の最も重要な点の一つは、スライスリテラルの導入です。`[]T{x1, x2, ... xn}` の形式でスライスを直接初期化できるようになりました。
* このスライスリテラルは、内部的には `[n]T{x1, x2, ... xn}[0 : n]` という配列リテラルを作成し、その全体を指すスライスを生成するショートカットであると説明されています。これにより、スライスが基となる配列へのビューであるというGo言語の設計原則が維持されつつ、より簡潔な初期化が可能になりました。
5. **`TODO` 項目の解決**:
* 以前の仕様書にあった「`new` が現状では奇妙である」という問題意識が、この複合リテラルの導入によって解決されたことが明示されました。これは、`new` が単なるメモリ割り当てに特化し、複合型の初期化は複合リテラルに任せるという設計方針の確立を示しています。
* また、配列の代入が許可されるようになったことも明記され、言語のセマンティクスがより明確になりました。
これらの変更は、Go言語の初期段階における言語設計の成熟を示しており、開発者がより効率的かつ直感的にコードを記述できるようになるための基盤を築きました。特にスライスリテラルの導入は、Go言語の普及と成功に不可欠な要素となりました。
## 関連リンク
* Go Programming Language Specification: [https://go.dev/ref/spec](https://go.dev/ref/spec) (現在のGo言語の公式仕様書)
* Go言語の初期の歴史に関するブログ記事やドキュメント(Go言語の進化を理解する上で役立つ可能性があります)
## 参考にした情報源リンク
* Go言語の公式ドキュメントおよび仕様書
* Go言語の初期のコミット履歴と関連する議論
* Go言語の `new` と `make` に関する解説記事
* Go言語の複合リテラルに関するチュートリアルやブログ記事