[インデックス 16955] ファイルの概要
このコミットは、Go言語仕様におけるインデックス式(a[i]
)とセレクタ式(a.f
)の挙動、特にポインタ型、nilスライス、定数文字列、型なし文字列に関する記述を明確化することを目的としています。これにより、言語のセマンティクスがより厳密になり、開発者の混乱を減らすことが期待されます。
コミット
- コミットハッシュ:
58d296065501958bbc7aeb8abea016b221b53f02
- Author: Robert Griesemer gri@golang.org
- Date: Wed Jul 31 13:40:01 2013 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/58d296065501958bbc7aeb8abea016b221b53f02
元コミット内容
spec: clarify index and selector expressions
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=rsc, r, iant, ken
CC=golang-dev
https://golang.org/cl/11884043
変更の背景
このコミットは、Go言語仕様のインデックス式とセレクタ式に関する曖昧さを解消するために行われました。特に、ポインタ型に対するインデックス操作やスライス操作、nilスライスの扱い、そして定数文字列や型なし文字列のインデックス/スライス結果の型と定数性に関する記述が不明確であったため、開発者が予期せぬ挙動に遭遇したり、言語のセマンティクスを誤解したりする可能性がありました。
具体的には、以下の問題が修正されました。
- Issue #4913: ポインタ型に対するインデックス操作の挙動が不明確であった点。例えば、
*A
型の変数a
に対してa[i]
やa[i:j]
と書いた場合に、それが(*a)[i]
や(*a)[i:j]
の糖衣構文として扱われることを明確にする必要がありました。 - Issue #5951: nilスライスの
len()
が定義されているにもかかわらず、インデックス操作やスライス操作においてnilオペランドが特別扱いされるケースがあった点。これにより、一貫性のない挙動が生じていました。
これらの修正により、Go言語の仕様がより堅牢で予測可能になり、開発者がより正確なコードを書けるようになることを目指しています。
前提知識の解説
このコミットの理解には、以下のGo言語の概念に関する知識が必要です。
- インデックス式 (Index Expressions): 配列、スライス、文字列、マップの要素にアクセスするための
a[i]
形式の式。 - セレクタ式 (Selector Expressions): 構造体のフィールドやインターフェースのメソッドにアクセスするための
x.f
形式の式。 - ポインタ (Pointers): 変数のメモリアドレスを保持する型。Goでは、ポインタのデリファレンス(
*p
)によって、ポインタが指す値にアクセスします。 - 配列 (Arrays): 固定長で同じ型の要素のシーケンス。
- スライス (Slices): 配列の一部を参照する動的なビュー。スライスは、基底配列、長さ(
len
)、容量(cap
)の3つの要素で構成されます。 - nilスライス (Nil Slices):
nil
値を持つスライス。Goでは、nilスライスは長さ0、容量0として扱われます。len(nil_slice)
は0
を返します。 - 文字列 (Strings): Goにおける文字列は、不変のバイトシーケンスです。UTF-8エンコードされたテキストを表現するために使用されます。
- 定数 (Constants): コンパイル時に値が決定される不変のデータ。Goには、型なし定数(untyped constants)と型付き定数(typed constants)があります。
- 型なし定数 (Untyped Constants): 特定の型を持たない定数。文脈に応じて適切な型に変換されます。例えば、
"hello"
は型なし文字列定数です。 - 型付き定数 (Typed Constants): 明示的に型が指定された定数。
- ランタイムパニック (Run-time Panic): プログラムの実行中に発生する回復不可能なエラー。Goでは、インデックスが範囲外である場合などにパニックが発生します。
- 糖衣構文 (Syntactic Sugar): プログラミング言語において、より複雑な構文を簡潔に記述できるようにするための構文。コンパイラによって、より基本的な構文に変換されます。
技術的詳細
このコミットは、Go言語仕様の doc/go_spec.html
ファイルを修正し、インデックス式とセレクタ式の挙動に関する以下の5つの主要な点を明確にしています。
-
ポインタ型に対するインデックス/スライス操作の明確化:
*A
型(配列へのポインタ)の変数a
に対してa[i]
やa[i:j]
と記述した場合、これはそれぞれ(*a)[i]
および(*a)[i:j]
の糖衣構文として扱われることを明記しました。- これにより、ポインタを介した配列要素へのアクセスやスライス作成がより直感的に行えるようになります。
-
nilオペランドの特別扱いの廃止:
len()
関数がnilスライスに対しても適切に定義されている(len(nil_slice)
は0
を返す)という事実に基づき、インデックス式やスライス式においてnilオペランドを特別扱いする必要がなくなりました。- 以前は、nilスライスに対する操作でパニックが発生する条件が複雑でしたが、この変更により、nilスライスも通常の長さ0のスライスとして一貫して扱われるようになります。
-
定数文字列のインデックス/スライス結果の定数性:
- 定数文字列をインデックス操作(
s[i]
)した場合の戻り値は、常に非定数(non-constant)のbyte
値となることを明確にしました。 - 定数文字列をスライス操作(
s[i:j]
)した場合の戻り値は、常に非定数(non-constant)のstring
値となることを明確にしました。 - これは、コンパイル時に文字列のインデックスやスライスの結果を定数として扱うことができないため、実行時の挙動と一致させるための重要な変更です。
- 定数文字列をインデックス操作(
-
型なし文字列のスライス結果の型:
- 型なし文字列(untyped string)をスライス操作(
s[i:j]
)した場合の戻り値は、常にstring
型の値となることを明確にしました。 - 型なし文字列は文脈に応じて型が推論されますが、スライス操作の結果は明示的に
string
型として扱われることで、型推論の複雑さを軽減し、予測可能な挙動を提供します。
- 型なし文字列(untyped string)をスライス操作(
-
有効なnilスライスのスライス結果:
- 有効なスライス式
a[i:j]
のオペランドがnilスライスである場合(この場合、i
とj
は範囲内ルールに従って0
でなければならない)、その結果もまたnilスライスとなることを明記しました。 - これは、nilスライスが長さ0、容量0のスライスとして扱われるという原則と一貫しており、スライス操作のセマンティクスをより厳密に定義します。
- 有効なスライス式
これらの変更は、Go言語の型システムと実行時セマンティクスをより正確に反映し、開発者が言語の挙動をより深く理解し、バグの少ないコードを書くのに役立ちます。
コアとなるコードの変更箇所
このコミットによる主要な変更は、Go言語仕様を記述した doc/go_spec.html
ファイルに対して行われました。具体的には、以下のセクションが修正されています。
- Index expressions (インデックス式): 配列、ポインタ、スライス、文字列、マップに対するインデックス操作のルールが更新されました。特に、ポインタ型に対するインデックス操作の糖衣構文が追加され、nilスライスや文字列のインデックス結果に関する記述が修正されました。
- Slice expressions (スライス式): スライス操作のルールが更新されました。ポインタ型に対するスライス操作の糖衣構文が追加され、定数文字列や型なし文字列のスライス結果の型と定数性、およびnilスライスのスライス結果に関する記述が修正されました。
変更は主にHTMLドキュメント内のテキストの追加、削除、修正であり、Goコンパイラやランタイムのコード自体への変更は含まれていません。これは、既存の挙動を仕様として明確化するものであり、言語の挙動そのものを変更するものではないためです。
コアとなるコードの解説
doc/go_spec.html
の変更点から、主要な修正箇所を抜粋して解説します。
インデックス式 (a[x]
) の変更
以前の記述:
<p>
denotes the element of the array, slice, string or map <code>a</code> indexed by <code>x</code>.
The value <code>x</code> is called the
<i>index</i> or <i>map key</i>, respectively. The following
rules apply:
</p>
変更後:
<p>
denotes the element of the array, pointer to array, slice, string or map <code>a</code> indexed by <code>x</code>.
The value <code>x</code> is called the <i>index</i> or <i>map key</i>, respectively.
The following rules apply:
</p>
解説: インデックス式が適用可能な型に「pointer to array」(配列へのポインタ)が明示的に追加されました。
配列へのポインタ型 *A
の a
に対するインデックス操作の新しいルールが追加されました。
<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
に対して a[x]
と書いた場合、それは (*a)[x]
の糖衣構文であることを明確にしています。つまり、ポインタを自動的にデリファレンスして、その指す配列の要素にアクセスします。
スライス型 S
の a
に対するインデックス操作のルールから、nil
スライスに関する記述が削除されました。
以前の記述:
<p>
For <code>a</code> of type <code>S</code> where <code>S</code> is a <a href="#Slice_types">slice type</a>:
</p>
<ul>
<li>if the slice is <code>nil</code> or if <code>x</code> is out of range at run time,
a <a href="#Run_time_panics">run-time panic</a> occurs</li>
<li><code>a[x]</code> is the slice element at index <code>x</code> and the type of
<code>a[x]</code> is the element type of <code>S</code></li>
</ul>
変更後:
<p>
For <code>a</code> of <a href="#Slice_types">slice type</a> <code>S</code>:
</p>
<ul>
<li>if <code>x</code> is out of range at run time,
a <a href="#Run_time_panics">run-time panic</a> occurs</li>
<li><code>a[x]</code> is the slice element at index <code>x</code> and the type of
<code>a[x]</code> is the element type of <code>S</code></li>
</ul>
解説: nil
スライスの場合の特別扱いが削除されました。これは、len()
が nil
スライスに対しても 0
を返すため、インデックスが 0
でない限り常に範囲外となりパニックが発生するという、より一般的なルールに統合されたためです。
文字列型 T
の a
に対するインデックス操作のルールが修正されました。
以前の記述:
<p>
For <code>a</code> of type <code>T</code>
where <code>T</code> is a <a href="#String_types">string type</a>:
</p>
<ul>
<li>a <a href="#Constants">constant</a> index must be in range
if the string <code>a</code> is also constant</li>
<li>if <code>x</code> is out of range at run time,
a <a href="#Run_time_panics">run-time panic</a> occurs</li>
<li><code>a[x]</code> is the byte at index <code>x</code> and the type of
<code>a[x]</code> is <code>byte</code></li>
<li><code>a[x]</code> may not be assigned to</li>
</ul>
変更後:
<p>
For <code>a</code> of <a href="#String_types">string type</a>:
</p>
<ul>
<li>a <a href="#Constants">constant</a> index must be in range
if the string <code>a</code> is also constant</li>
<li>if <code>x</code> is out of range at run time,
a <a href="#Run_time_panics">run-time panic</a> occurs</li>
<li><code>a[x]</code> is the non-constant byte value at index <code>x</code> and the type of
<code>a[x]</code> is <code>byte</code></li>
<li><code>a[x]</code> may not be assigned to</li>
</ul>
解説: a[x]
の結果が「non-constant byte value」(非定数バイト値)であることが明記されました。これにより、定数文字列をインデックス操作しても、その結果は定数ではないことが明確になります。
スライス式 (a[low : high]
) の変更
配列へのポインタ型 *A
の a
に対するスライス操作の新しいルールが追加されました。
<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>
解説: インデックス式と同様に、配列へのポインタに対するスライス操作も糖衣構文として (*a)[low : high]
に変換されることが明確にされました。
スライス操作のインデックス範囲チェックと結果の型に関する記述が修正されました。 以前の記述:
<p>
For arrays or strings, the indices <code>low</code> and <code>high</code> are
<i>in range</i> if <code>0</code> <= <code>low</code> <= <code>high</code> <= <code>len(a)</code>,
otherwise they are <i>out of range</i>.
For slices, the upper index bound is the slice capacity <code>cap(a)</code> rather than the length.
A <a href="#Constants">constant</a> index must be non-negative and representable by a value of type
<code>int</code>.
If both indices
are constant, they must satisfy <code>low <= high</code>. If <code>a</code> is <code>nil</code>
or if the indices are out of range at run time, a <a href="#Run_time_panics">run-time panic</a> occurs.
</p>
<p>
If the sliced operand is a string or slice, the result of the slice operation
is a string or slice of the same type.
If the sliced operand is an array, it must be <a href="#Address_operators">addressable</a>
and the result of the slice operation is a slice with the same element type as the array.
</p>
変更後:
<p>
For arrays or strings, the indices are <i>in range</i> if
<code>0</code> <= <code>low</code> <= <code>high</code> <= <code>len(a)</code>,
otherwise they are <i>out of range</i>.
For slices, the upper index bound is the slice capacity <code>cap(a)</code> rather than the length.
A <a href="#Constants">constant</a> index must be non-negative and representable by a value of type
<code>int</code>.
If both indices are constant, they must satisfy <code>low <= high</code>.
If the indices are out of range at run time, a <a href="#Run_time_panics">run-time panic</a> occurs.
</p>
<p>
Except for <a href="#Constants">untyped strings</a>, if the sliced operand is a string or slice,\n+the result of the slice operation is a non-constant value of the same type as the operand.\n+For untyped string operands the result is a non-constant value of type <code>string</code>.\n If the sliced operand is an array, it must be <a href="#Address_operators">addressable</a>
and the result of the slice operation is a slice with the same element type as the array.
</p>
解説:
nil
オペランドに関するパニックの記述が削除され、より一般的な「インデックスが範囲外の場合にパニック」というルールに統合されました。- スライス結果の定数性について、「Except for untyped strings」(型なし文字列を除く)という条件が追加され、スライス結果が「non-constant value」(非定数値)であることが明記されました。
- 型なし文字列のスライス結果は、常に「non-constant value of type
string
」(非定数なstring
型の値)となることが明確にされました。
nilスライスのスライス結果に関する新しい段落が追加されました。
<!-- TODO: should this be an implementation restriction? -->
<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>
解説: 有効なスライス式において、オペランドが nil
スライスである場合、その結果も nil
スライスとなることが明記されました。これは、len(nil_slice)
が 0
であるため、a[0:0]
のみが有効なスライス操作となり、その結果も nil
スライスとなるという一貫した挙動を保証します。
これらの変更は、Go言語の仕様をより正確で、曖昧さのないものにし、開発者が言語の挙動をより深く理解できるようにすることを目的としています。
関連リンク
- Go CL 11884043: https://golang.org/cl/11884043
- Go Issue #4913: https://code.google.com/p/go/issues/detail?id=4913 (現在はGitHubに移行: https://github.com/golang/go/issues/4913)
- Go Issue #5951: https://code.google.com/p/go/issues/detail?id=5951 (現在はGitHubに移行: https://github.com/golang/go/issues/5951)
参考にした情報源リンク
- Go Programming Language Specification: https://go.dev/ref/spec (コミットが適用された後の最新版)
- Go Slices: usage and internals: https://go.dev/blog/slices-usage-and-internals
- Go Data Structures: https://go.dev/blog/go-data-structures
- Go Constants: https://go.dev/ref/spec#Constants
- Go Pointers: https://go.dev/ref/spec#Pointer_types
- Go Index expressions: https://go.dev/ref/spec#Index_expressions
- Go Slice expressions: https://go.dev/ref/spec#Slice_expressions
- Go Strings: https://go.dev/blog/strings
- Go Panic and Recover: https://go.dev/blog/defer-panic-and-recover