[インデックス 13987] ファイルの概要
このコミットは、Go言語の公式仕様書である doc/go_spec.html
の内容を更新するものです。具体的には、append
および copy
という組み込み関数が、引数として渡されるスライスのメモリ領域が重複している場合にどのように振る舞うかについて、その仕様を明確化しています。
コミット
commit 0c494718afb00c4885638658e07f65ed0bc1c5e3
Author: Robert Griesemer <gri@golang.org>
Date: Fri Sep 28 15:55:38 2012 -0700
go spec: arguments for append may overlap
Fixes #4142.
R=rsc, r, iant, ken, remyoudompheng
CC=golang-dev
https://golang.org/cl/6567062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0c494718afb00c4885638658e07f65ed0bc1c5e3
元コミット内容
このコミットは、Go言語の仕様書において、append
関数の引数がメモリ上で重複する場合の挙動を明確にするものです。具体的には、append
および copy
関数の両方について、引数によって参照されるメモリが重複しているかどうかに関わらず、結果が独立していることを明記しています。これは、Go言語の組み込み関数が、このような状況でも予測可能な正しい動作を保証することを示す重要な変更です。
変更の背景
この変更の背景には、Go言語の組み込み関数である append
と copy
が、引数として渡されるスライスの基盤となる配列のメモリ領域が重複している場合に、どのように振る舞うべきかという仕様上の曖昧さがありました。特に append
関数については、copy
関数がメモリ重複を正しく処理することが知られている一方で、append
も同様の保証をするのかどうかについて、開発者コミュニティ内で議論がありました。
この曖昧さは、Go Issue #4142「spec: does append use memmove like copy?」で提起されました。このIssueでは、append
が重複するオペランドに対して memmove
のような動作を保証するかどうかが議論の焦点となりました。もし保証がないと明記された場合でも、特定のGoの実装がたまたま正しい動作をする可能性があり、それが開発者の誤解を招く恐れがありました。
このコミットは、このような曖昧さを解消し、append
および copy
の両関数が、引数のメモリ重複に関わらず「正しいこと(the right thing)」を行うことを明確に仕様に記述することで、開発者がこれらの関数をより安心して使用できるようにすることを目的としています。これにより、Goプログラムの予測可能性と堅牢性が向上します。
前提知識の解説
Go言語のスライス (Slices)
Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、基盤となる配列へのポインタ、スライスの長さ(len
)、およびスライスの容量(cap
)の3つの要素で構成されます。スライスは動的なサイズ変更が可能であり、Go言語で可変長シーケンスを扱うための主要な手段です。
- 長さ (Length): スライスに含まれる要素の数。
len(s)
で取得できます。 - 容量 (Capacity): スライスの基盤となる配列の、スライスが拡張できる最大要素数。
cap(s)
で取得できます。
スライスは、同じ基盤となる配列を共有することができます。これにより、複数のスライスが同じメモリ領域の一部または全体を参照する「メモリ重複」が発生する可能性があります。
append
関数
append
は、スライスに要素を追加するための組み込み関数です。
append(s []T, elems ...T) []T
s
にelems
を追加した新しいスライスを返します。- もし
s
の基盤となる配列に十分な容量があれば、append
は既存の配列に要素を追加し、新しいスライスヘッダを返します。 - 容量が不足している場合、
append
はより大きな新しい配列を割り当て、既存の要素と新しい要素をその新しい配列にコピーし、新しいスライスヘッダを返します。
copy
関数
copy
は、ソーススライスからデスティネーションスライスに要素をコピーするための組み込み関数です。
copy(dst, src []T) int
src
からdst
に要素をコピーし、コピーされた要素の数を返します。- コピーされる要素の数は、
len(dst)
とlen(src)
の小さい方です。 copy
関数は、ソースとデスティネーションのスライスが同じ基盤となる配列を共有し、メモリ領域が重複している場合でも、正しく動作することが保証されています。これは、copy
が内部的にmemmove
のようなセマンティクス(メモリ重複を考慮したコピー)で実装されているためです。
メモリ重複 (Overlapping Memory)
メモリ重複とは、複数のスライスが同じ基盤となる配列の同じ、または一部のメモリ領域を参照している状態を指します。例えば、あるスライス s
があり、その一部を切り出した新しいスライス s[i:j]
を作成した場合、これら2つのスライスは同じ基盤となる配列を共有するため、メモリが重複しています。
このような状況で append
や copy
のような操作を行う場合、関数の実装がメモリ重複を正しく処理しないと、予期せぬ結果(例えば、コピー元がコピー中に上書きされてしまうなど)が生じる可能性があります。
技術的詳細
このコミットは、Go言語の仕様書 doc/go_spec.html
の「Appending to and copying slices」セクションに、以下の重要な文言を追加することで、append
および copy
関数の挙動を明確にしています。
For both functions, the result is independent of whether the memory referenced
by the arguments overlaps.
この一文は、「両関数(append
と copy
)において、引数によって参照されるメモリが重複しているかどうかに関わらず、結果は独立している」と明記しています。
これは、Go言語の設計思想における重要な保証です。つまり、開発者は append
や copy
を使用する際に、入力スライスがメモリ上で重複しているかどうかを心配する必要がないということです。これらの関数は、内部的にメモリ重複を適切に処理し、常に正しい結果を返します。
特に append
については、容量が不足した場合に新しい基盤配列を割り当てるため、多くの場合メモリ重複の問題は発生しません。しかし、既存の容量内で要素が追加される場合や、append(s, s...)
のようにスライス自身を引数として渡すようなケースでは、メモリ重複が発生する可能性があります。この仕様の明確化により、そのようなエッジケースにおいても append
が予測可能な正しい動作をすることが保証されます。
この変更は、Go言語の堅牢性と予測可能性を高め、開発者がスライス操作をより自信を持って行えるようにするためのものです。
コアとなるコードの変更箇所
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
<!--{
"Title": "The Go Programming Language Specification",
- "Subtitle": "Version of September 26, 2012",
+ "Subtitle": "Version of September 28, 2012",
"Path": "/ref/spec"
}-->
@@ -4903,7 +4903,10 @@ m := make(map[string]int, 100) // map with initial space for 100 elements
<h3 id="Appending_and_copying_slices">Appending to and copying slices</h3>
<p>
-Two built-in functions assist in common slice operations.
+The built-in functions <code>append</code> and <code>copy</code> assist in
+common slice operations.\n+For both functions, the result is independent of whether the memory referenced
+by the arguments overlaps.
</p>
<p>
@@ -4934,21 +4937,22 @@ slice may refer to a different underlying array.\n
<pre>\n s0 := []int{0, 0}\n-s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2}\n-s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7}\n-s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}\n+s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2}\n+s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7}\n+s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}\n+s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}\n \n var t []interface{}\n-t = append(t, 42, 3.1415, \"foo\") t == []interface{}{42, 3.1415, \"foo\"}\n+t = append(t, 42, 3.1415, \"foo\") t == []interface{}{42, 3.1415, \"foo\"}\n \n var b []byte\n-b = append(b, \"bar\"...) // append string contents b == []byte{'b', 'a', 'r' }\n+b = append(b, \"bar\"...) // append string contents b == []byte{'b', 'a', 'r' }\n </pre>\n \n <p>\n The function <code>copy</code> copies slice elements from\n a source <code>src</code> to a destination <code>dst</code> and returns the\n-number of elements copied. Source and destination may overlap.\n+number of elements copied.\n Both arguments must have <a href=\"#Type_identity\">identical</a> element type <code>T</code> and must be\n <a href=\"#Assignability\">assignable</a> to a slice of type <code>[]T</code>.\n The number of elements copied is the minimum of\n```
## コアとなるコードの解説
このコミットの主要な変更点は、Go言語の仕様書 `doc/go_spec.html` の以下の箇所です。
1. **仕様書のバージョン日付の更新**:
`- "Subtitle": "Version of September 26, 2012",`
`+ "Subtitle": "Version of September 28, 2012",`
これは、仕様書が更新された日付を反映しています。
2. **`append` と `copy` のメモリ重複に関する明確化**:
`-Two built-in functions assist in common slice operations.`
`+The built-in functions <code>append</code> and <code>copy</code> assist in`
`+common slice operations.`
`+For both functions, the result is independent of whether the memory referenced`
`+by the arguments overlaps.`
この部分が最も重要な変更です。以前は単に「2つの組み込み関数が一般的なスライス操作を助ける」と書かれていましたが、変更後は `append` と `copy` が明示され、さらに「両関数において、引数によって参照されるメモリが重複しているかどうかに関わらず、結果は独立している」という保証が追加されました。これにより、これらの関数がメモリ重複を適切に処理することが明確に示されます。
3. **`append` の例にメモリ重複のケースを追加**:
`+s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}`
`append` の使用例に、`s3[3:6]` と `s3[2:]` のようにメモリが重複するスライスを `append` する具体的な例が追加されました。この例は、`append` がこのような状況でも期待通りの結果を返すことを示しており、上記の仕様変更を補強するものです。
4. **`copy` の説明から「Source and destination may overlap.」の削除**:
`-number of elements copied. Source and destination may overlap.`
`+number of elements copied.`
以前は `copy` の説明に「Source and destination may overlap.」と明示的に書かれていましたが、今回の変更で `append` と `copy` の両方に共通の保証が追加されたため、`copy` の個別の説明からはこの文言が削除されました。これは冗長性を排除し、新しい共通の記述に集約するためです。
これらの変更は、Go言語の仕様をより正確で包括的なものにし、開発者がスライス操作の挙動について明確な理解を持てるようにすることを目的としています。
## 関連リンク
* Go Issue #4142: [spec: does append use memmove like copy?](https://github.com/golang/go/issues/4142)
## 参考にした情報源リンク
* Web search results for "Go slices append copy overlapping memory"
* Web search results for "Go issue 4142"
* The Go Programming Language Specification: [https://go.dev/ref/spec](https://go.dev/ref/spec) (コミット時点のバージョンとは異なる可能性がありますが、一般的なGoの仕様理解に役立ちます)
* Go Slices: usage and internals: [https://go.dev/blog/slices](https://go.dev/blog/slices) (Goスライスの内部動作に関する公式ブログ記事)