[インデックス 16972] ファイルの概要
このコミットは、Go言語の仕様書(doc/go_spec.html
)を更新し、インデックス式(a[i]
)とセレクタ式(a[i:j]
、スライス)に関する記述を明確にすることを目的としています。特に、ポインタ型に対するインデックス操作やスライス操作の挙動、nil
スライスの扱い、定数文字列のインデックス/スライス結果の型について、より厳密な定義が追加されています。
コミット
commit 2961d229de95e8d62e673080a616396fe4da9a3f
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jul 31 22:25:47 2013 -0700
spec: clarify index and selector expressions
(Replacement for CL 11884043.)
1) Explain a[i] and a[i:j] where a is of type *A as
shortcut for (*a)[i] and (*a)[i:j], respectively.
2) Together with 1), because len() of nil slices is
well defined, there's no need to special case nil
operands anymore.
3) The result of indexing or slicing a constant string
is always a non-constant byte or string value.
4) The result of slicing an untyped string is a value
of type string.
5) If the operand of a valid slice a[i:j] is nil (i, j
must be 0 for it to be valid - this already follows
from the in-range rules), the result is a nil slice.
Fixes #4913.
Fixes #5951.
R=r, rsc, iant, ken
CC=golang-dev
https://golang.org/cl/12198043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2961d229de95e8d62e673080a616396fe4da9a3f
元コミット内容
このコミットは、Go言語の仕様書(doc/go_spec.html
)におけるインデックス式とセレクタ式(スライス)の記述を明確化するものです。主な変更点は以下の通りです。
- ポインタ型に対するインデックス/スライス操作の明確化: 型
*A
(A
は配列型)の変数a
に対するa[i]
やa[i:j]
といった操作が、それぞれ(*a)[i]
や(*a)[i:j]
のショートカットとして解釈されることを明記しました。これにより、ポインタのデリファレンスが暗黙的に行われることが明確になります。 nil
オペランドの特殊ケースの削除: 上記1)の変更と、len()
関数がnil
スライスに対しても適切に定義されている(len(nil)
は0を返す)という事実により、インデックス操作やスライス操作においてnil
オペランドを特別扱いする必要がなくなりました。- 定数文字列のインデックス/スライス結果の型: 定数文字列をインデックスまたはスライスした場合の結果が、常に非定数の
byte
またはstring
値となることを明確にしました。 - 型なし文字列のスライス結果の型: 型なし文字列(untyped string)をスライスした場合の結果が、
string
型となることを明確にしました。 - 有効な
nil
スライスオペランドの挙動: 有効なスライス式a[i:j]
において、オペランドa
がnil
スライスである場合(この場合、i
とj
は範囲内ルールに従って0でなければならない)、結果もnil
スライスとなることを明記しました。
これらの変更は、Go言語の仕様の曖昧さを解消し、コンパイラの実装や開発者の理解をより一貫性のあるものにすることを目的としています。
変更の背景
このコミットは、Go言語のインデックス式とセレクタ式(スライス)に関する既存の仕様記述が、特定のケースにおいて曖昧であったり、開発者の間で誤解を招く可能性があったために行われました。特に、以下の問題が背景にありました。
- ポインタ型に対するインデックス/スライス操作の挙動の不明確さ: Go言語では、配列へのポインタに対して直接インデックス操作やスライス操作を行うことができますが、その際の内部的な挙動(デリファレンスが暗黙的に行われること)が仕様書で明示されていませんでした。これにより、特にC/C++などの言語に慣れた開発者が混乱する可能性がありました。
nil
スライスの扱いに関する混乱:nil
スライスはGoにおいて有効なスライスであり、len(nil)
は0を返します。しかし、インデックス操作やスライス操作においてnil
スライスがどのように扱われるべきか、特にパニックが発生する条件などが十分に明確ではありませんでした。これにより、nil
チェックの必要性や、意図しないランタイムパニックの発生につながる可能性がありました。- 定数文字列および型なし文字列のインデックス/スライス結果の型の曖昧さ: Go言語には「型なし定数(untyped constants)」という概念があり、文字列定数もその一種です。これらの定数をインデックスしたりスライスしたりした場合に、結果がどのような型になるのか、またそれが定数として扱われるのか非定数として扱われるのかが不明確でした。これは、コンパイラが型推論を行う上で重要な情報であり、仕様として明確にする必要がありました。
これらの問題は、Go言語のIssueトラッカーで報告されたバグ(Fixes #4913
, Fixes #5951
)として顕在化しました。
- Issue #4913: spec: clarify indexing of pointers to arrays: 配列へのポインタのインデックスに関する仕様の明確化を求めるものでした。
- Issue #5951: spec: clarify slicing of untyped strings: 型なし文字列のスライスに関する仕様の明確化を求めるものでした。
これらのIssueに対応するため、Robert Griesemer氏(Go言語の共同設計者の一人)によって、仕様書の該当箇所が修正・追記されました。このコミットは、以前の変更(CL 11884043)の置き換えとして提出され、より包括的な修正となっています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念と仕様に関する知識が必要です。
-
配列 (Arrays):
- Goの配列は、同じ型の要素を固定長で連続して格納するデータ構造です。
[N]T
のように宣言され、N
は配列の長さ(要素数)、T
は要素の型です。- 配列は値型であり、変数に代入されるとコピーされます。
-
スライス (Slices):
- Goのスライスは、配列の一部を参照する動的なビューです。
[]T
のように宣言され、基になる配列、長さ(len
)、容量(cap
)の3つの要素から構成されます。- スライスは参照型であり、変数に代入されてもコピーされず、同じ基になる配列を参照します。
nil
スライス:var s []int
のように宣言されたスライスは初期値がnil
です。nil
スライスは長さ0、容量0を持ち、基になる配列を持たない有効なスライスです。len(nil)
は0、cap(nil)
は0を返します。
-
ポインタ (Pointers):
- Goのポインタは、変数のメモリアドレスを保持する変数です。
*T
のように宣言され、T
はポインタが指す変数の型です。&
演算子で変数のアドレスを取得し、*
演算子でポインタが指す値(デリファレンス)を取得します。
-
インデックス式 (Index Expressions):
- 配列、スライス、文字列、マップの要素にアクセスするために使用されます。
a[i]
の形式で、a
はオペランド、i
はインデックスまたはマップキーです。- 配列、スライス、文字列の場合、インデックス
i
は0から始まり、len(a)-1
までが有効範囲です。範囲外のインデックスにアクセスしようとすると、ランタイムパニック(panic
)が発生します。
-
スライス式 (Slice Expressions):
- 配列、配列へのポインタ、スライス、文字列から新しいスライスまたは部分文字列を作成するために使用されます。
a[low:high]
、a[low:]
、a[:high]
、a[:]
の形式があります。low
は開始インデックス(省略時は0)、high
は終了インデックス(省略時はlen(a)
)です。結果のスライスにはlow
からhigh-1
までの要素が含まれます。- スライスの場合、
high
はcap(a)
以下でなければなりません。 - インデックスが範囲外の場合、ランタイムパニックが発生します。
-
文字列 (Strings):
- Goの文字列は、不変のバイトシーケンスです。
- 文字列をインデックスすると、その位置のバイト値(
byte
型)が返されます。 - 文字列をスライスすると、新しい文字列が返されます。
-
定数 (Constants):
- Goの定数は、コンパイル時に決定される不変の値です。
- 数値、ブール値、文字列の定数があります。
- Goには「型なし定数(untyped constants)」という概念があり、これらは特定の型を持たず、使用される文脈によって型が推論されます。例えば、
"hello"
は型なし文字列定数です。
-
ランタイムパニック (Run-time Panics):
- Goプログラムの実行中に発生する回復不可能なエラーです。
- インデックスが範囲外の場合や、
nil
ポインタのデリファレンスなど、プログラムの論理的な欠陥を示す状況で発生します。
これらの概念を理解することで、コミットがGo言語のどのような挙動を明確化し、なぜそれが重要であるのかを深く把握することができます。
技術的詳細
このコミットは、Go言語の仕様書(doc/go_spec.html
)の「Index expressions」と「Slice expressions」のセクションに具体的な変更を加えています。
1. ポインタ型に対するインデックス/スライス操作の明確化
変更前:
配列型A
または*A
のa
に対するインデックス操作について、a[x]
が配列要素を指すという一般的な記述はありましたが、*A
の場合の暗黙的なデリファレンスについては明示されていませんでした。
変更後: 「Index expressions」セクションに以下の新しい項目が追加されました。
<p>
For <code>a</code> of <a href="#Pointer_types">pointer</a> to array type:
</p>
<ul>
<li><code>a[x]</code> is shorthand for <code>(*a)[x]</code></li>
</ul>
これにより、配列へのポインタa
に対するインデックス操作a[x]
が、まずポインタa
をデリファレンスして*a
とし、その結果の配列に対してインデックス操作(*a)[x]
を行うことのショートカットであることが明確にされました。同様に、スライス式についても以下の記述が追加されました。
<p>
If <code>a</code> is a pointer to an array, <code>a[low : high]</code> is shorthand for
<code>(*a)[low : high]</code>.
</p>
これは、配列へのポインタに対するスライス操作も、同様に暗黙的なデリファレンスが行われることを示しています。この明確化により、コンパイラの実装者はこの挙動を正しく処理し、開発者はポインタを介した配列アクセスがどのように機能するかを正確に理解できるようになります。
2. nil
オペランドの特殊ケースの削除とパニック条件の明確化
変更前:
インデックス式やスライス式において、オペランドがnil
である場合にパニックが発生する条件が、配列、スライス、文字列の各セクションで個別に記述されており、特にスライスに関しては「if the slice is nil
or if x
is out of range at run time, a run-time panic occurs」という記述がありました。これは、nil
スライス自体が有効なスライスであり、len(nil)
が0を返すという事実と矛盾する可能性がありました。
変更後:
インデックス式において、配列とスライスのパニック条件から「if a
is nil
」または「if the slice is nil
」という記述が削除され、単に「if x
is out of range at run time, a run-time panic occurs」となりました。
これは、len(nil)
が0であるため、nil
スライスに対して有効なインデックスは存在せず、結果として常にインデックスが範囲外となりパニックが発生するという、より一般的なルールに集約できるためです。
スライス式についても同様に、「If a
is nil
or if the indices are out of range at run time, a run-time panic occurs.」から「If the indices are out of range at run time, a run-time panic occurs.」に変更されました。
さらに、スライス式に関して、有効なnil
スライスオペランドの挙動を明確にする新しいパラグラフが追加されました。
<p>
If the sliced operand of a valid slice expression is a <code>nil</code> slice, the result
is a <code>nil</code> slice.
<p>
これは、a[0:0]
のようにnil
スライスに対して有効なスライス操作を行った場合、結果もnil
スライスになることを明示しています。これは、nil
スライスがGoの設計において特別な意味を持つことを強調しています。
3. 定数文字列のインデックス/スライス結果の型
変更前:
文字列のインデックス操作a[x]
の結果がbyte
型であることは記述されていましたが、定数文字列をインデックスした場合に結果が定数になるのか非定数になるのかは不明確でした。
変更後:
文字列のインデックス操作について、「a[x]
is the byte at index x
and the type of a[x]
is byte
」から「a[x]
is the non-constant byte value at index x
and the type of a[x]
is byte
」に変更されました。
これにより、定数文字列をインデックスした場合でも、結果は常に非定数のbyte
値となることが明確になりました。
4. 型なし文字列のスライス結果の型
変更前: スライス式の結果の型について、「If the sliced operand is a string or slice, the result of the slice operation is a string or slice of the same type.」と記述されていました。型なし文字列の場合にどうなるかは不明確でした。
変更後: スライス式の結果の型について、以下の記述が追加されました。
Except for <a href="#Constants">untyped strings</a>, if the sliced operand is a string or slice,
the result of the slice operation is a non-constant value of the same type as the operand.
+For untyped string operands the result is a non-constant value of type <code>string</code>.
これにより、型なし文字列をスライスした場合、結果は非定数のstring
型となることが明確になりました。これは、型なし定数が文脈によって型を持つようになるGoの型システムの一貫性を保つための重要な詳細です。
これらの変更は、Go言語の型システムとランタイムの挙動に関する厳密な定義を提供し、言語の予測可能性と堅牢性を向上させています。
コアとなるコードの変更箇所
このコミットは、Go言語の仕様書である doc/go_spec.html
ファイルのみを変更しています。
具体的には、以下のセクションが修正されています。
-
Index expressions (インデックス式):
- ポインタ型(
*A
)に対するインデックス操作a[x]
が(*a)[x]
のショートカットであることの記述が追加されました。 - 配列およびスライスのインデックス操作におけるパニック条件から、「
nil
である場合」の記述が削除され、範囲外の場合に限定されました。 - 文字列のインデックス操作の結果が「非定数(non-constant)」の
byte
値であることの記述が追加されました。
- ポインタ型(
-
Slice expressions (スライス式):
- ポインタ型(
*A
)に対するスライス操作a[low:high]
が(*a)[low:high]
のショートカットであることの記述が追加されました。 - スライス操作におけるパニック条件から、「
nil
である場合」の記述が削除され、範囲外の場合に限定されました。 - 型なし文字列のスライス結果が非定数の
string
型であることの記述が追加されました。 - 有効な
nil
スライスオペランドのスライス結果がnil
スライスであることの記述が追加されました。
- ポインタ型(
これらの変更は、HTMLドキュメント内のテキストの追加、削除、修正によって行われています。
コアとなるコードの解説
このコミットにおける「コアとなるコード」とは、Go言語の仕様書であるdoc/go_spec.html
内の特定のHTML要素とテキストの変更を指します。Go言語のコンパイラやランタイムのソースコード自体が変更されているわけではありませんが、これらの仕様変更は、将来のコンパイラ実装や既存のコンパイラの挙動の解釈に影響を与えます。
変更された主要な箇所とその意味は以下の通りです。
-
ポインタ型に対するインデックス/スライス操作の明確化:
doc/go_spec.html
の「Index expressions」セクションに、配列へのポインタa
に対するa[x]
が(*a)[x]
のショートカットであるという新しい<li>
要素が追加されました。- 同様に、「Slice expressions」セクションに、配列へのポインタ
a
に対するa[low:high]
が(*a)[low:high]
のショートカットであるという新しい<p>
要素が追加されました。 - 解説: これは、Go言語がポインタの自動デリファレンス(dereferencing)を特定の文脈で行うことを明示しています。C言語などでは
(*a)[i]
と明示的に書く必要がありますが、Goでは利便性のためにa[i]
と書くことが許容されており、その挙動が仕様として明確化されました。これにより、開発者はポインタを介した配列やスライスへのアクセスが、値型へのアクセスと同じように直感的に行えることを理解できます。
-
nil
オペランドの特殊ケースの削除とパニック条件の明確化:- 「Index expressions」セクションの配列とスライスの項目で、パニック条件の記述から「if
a
isnil
」や「if the slice isnil
」といった文言が削除されました。 - 「Slice expressions」セクションでも同様に、パニック条件から「If
a
isnil
」という文言が削除されました。 - 代わりに、「If the indices are out of range at run time, a run-time panic occurs.」という記述に統一されました。
- さらに、「Slice expressions」セクションに、「If the sliced operand of a valid slice expression is a
nil
slice, the result is anil
slice.」という新しい<p>
要素が追加されました。 - 解説: この変更は、
nil
スライスがGoにおいて有効なスライスであり、その長さが0であるという事実に基づいています。len(nil)
が0であるため、nil
スライスに対して有効なインデックスは存在しません(常に範囲外となる)。したがって、「nil
であること」をパニックの直接の原因として特別扱いする必要がなくなり、「範囲外であること」というより一般的なルールに集約されました。また、nil
スライスをスライスした場合にnil
スライスが返るという挙動を明記することで、nil
スライスの扱いに関する曖昧さを解消し、より予測可能な挙動を保証しています。
- 「Index expressions」セクションの配列とスライスの項目で、パニック条件の記述から「if
-
定数文字列のインデックス/スライス結果の型:
- 「Index expressions」セクションの文字列の項目で、
a[x]
の結果が「the non-constant byte value」であるという記述に修正されました。 - 解説: Goの定数はコンパイル時に評価され、型なし定数は文脈によって型が推論されます。しかし、文字列のインデックス操作の結果は、たとえ元の文字列が定数であっても、実行時に評価されるバイト値であり、もはやコンパイル時の定数としては扱われないことを明確にしています。これにより、コンパイラが生成するコードの型推論と最適化がより正確になります。
- 「Index expressions」セクションの文字列の項目で、
-
型なし文字列のスライス結果の型:
- 「Slice expressions」セクションで、型なし文字列をスライスした場合の結果が「a non-constant value of type
string
」であるという新しい記述が追加されました。 - 解説: 型なし文字列は、スライス操作によって具体的な
string
型を持つ非定数値に変換されることを示しています。これは、Goの型なし定数の挙動の一貫性を保ち、コンパイラが型なし文字列のスライス結果をどのように扱うべきかを明確に定義しています。
- 「Slice expressions」セクションで、型なし文字列をスライスした場合の結果が「a non-constant value of type
これらの変更は、Go言語のセマンティクス(意味論)をより厳密に定義し、言語の設計意図を開発者とコンパイラ実装者の双方に明確に伝える上で非常に重要です。
関連リンク
- Go言語の公式仕様書: https://go.dev/ref/spec (このコミットで変更されたドキュメントの最新版)
- Go Issue #4913: spec: clarify indexing of pointers to arrays: https://github.com/golang/go/issues/4913
- Go Issue #5951: spec: clarify slicing of untyped strings: https://github.com/golang/go/issues/5951
- Gerrit Change-ID 12198043 (このコミットに対応するGoのコードレビューシステムのエントリ): https://golang.org/cl/12198043
参考にした情報源リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語のブログ (特にGoの設計に関する記事): https://go.dev/blog/
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語のIssueトラッカー: https://github.com/golang/go/issues
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/
- Go言語の仕様に関する議論が行われるメーリングリスト (golang-dev): https://groups.google.com/g/golang-dev
- Go Slices: usage and internals: https://go.dev/blog/slices-intro (スライスの挙動に関する詳細な解説)
- Go Data Structures: Interfaces: https://go.dev/blog/go-data-structures-interfaces (Goの型システムに関する理解を深めるための参考)
- Go Constants: https://go.dev/blog/constants (Goの定数に関する詳細な解説)
- Go Pointers: https://go.dev/doc/effective_go#pointers (Goのポインタに関する基本的な解説)
- Go Panic and Recover: https://go.dev/blog/defer-panic-and-recover (Goのパニックに関する基本的な解説)
- Go Strings, bytes, runes and characters in Go: https://go.dev/blog/strings (Goの文字列に関する詳細な解説)