[インデックス 16615] ファイルの概要
このコミットは、Go言語の仕様書(doc/go_spec.html
)における文字列とバイトスライス、およびルーンスライスの変換に関する記述を、実際のコンパイラの挙動に合わせて修正するものです。特に、空の文字列をバイトスライスやルーンスライスに変換した際の挙動、およびnil
のスライスを文字列に変換した際の挙動について、仕様と実装の乖離を解消し、より明確な記述と例を追加しています。
コミット
commit de47f68c99e9c86a32261b45752819a86cac74f2
Author: Robert Griesemer <gri@golang.org>
Date: Fri Jun 21 16:11:13 2013 -0700
spec: fix spec on conversions to match implementations
The existing compilers convert empty strings to empty
but non-nil byte and rune slices. The spec required
a nil byte and rune slice in those cases. That seems
an odd additional requirement. Adjust the spec to
match the reality.
Also, removed over-specification for conversions of
nil []byte and []rune: such nil slices already act
like empty slices and thus don't need extra language.
Added extra examples instead.
Fixes #5704.
R=rsc, r, iant
CC=golang-dev
https://golang.org/cl/10440045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/de47f68c99e9c86a32261b45752819a86cac74f2
元コミット内容
既存のコンパイラは、空の文字列を空ではあるがnil
ではないバイトスライスやルーンスライスに変換していました。しかし、当時のGo言語の仕様書では、これらのケースでnil
のバイトスライスやルーンスライスを返すことを要求していました。これは奇妙な追加要件であると判断され、仕様を実際のコンパイラの挙動に合わせるように修正されました。
また、nil
の[]byte
や[]rune
を文字列に変換する際の過剰な仕様記述が削除されました。これらのnil
スライスは既に空のスライスと同様に振る舞うため、特別な記述は不要とされました。代わりに、より明確な例が追加されています。
このコミットは、Go言語のIssue 5704を修正するものです。
変更の背景
Go言語では、文字列型とバイトスライス([]byte
)、ルーンスライス([]rune
)の間で相互変換が可能です。これらの変換は、特に文字列のエンコーディング(Goの文字列はUTF-8でエンコードされたバイト列)や、Unicodeコードポイント(ルーン)の操作において非常に重要です。
このコミットが行われた背景には、Go言語の仕様書と実際のコンパイラ(gcや6gなど)の実装との間に、特定のケースでの挙動の不一致があったことが挙げられます。具体的には、空の文字列をスライスに変換する際、仕様では結果がnil
スライスになるべきだとされていましたが、実際のコンパイラはnil
ではない空のスライスを生成していました。
この不一致は、開発者が仕様書を読んで期待する挙動と、実際にコードを実行した際の挙動が異なるという混乱を招く可能性がありました。特に、スライスがnil
であるかどうかのチェックは、プログラムのロジックにおいて重要な意味を持つ場合があるため、この不一致は潜在的なバグの原因となり得ました。
Go言語の設計哲学の一つに「実用性」があります。この場合、コンパイラの既存の挙動がより実用的であり、かつnil
ではない空のスライスを返すことが、多くのユースケースにおいて自然であると判断されたため、仕様の方を実装に合わせるという決定がなされました。また、nil
のスライスが空のスライスと同様に扱われるというGoの特性を考慮すると、nil
スライスから文字列への変換に関する過剰な記述も不要と判断されました。
この変更は、Go言語の仕様の正確性を高め、開発者がより信頼性の高いコードを書けるようにするための重要なステップでした。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。
-
文字列 (string):
- Goの文字列は、不変のバイトスライスであり、通常はUTF-8でエンコードされたUnicodeコードポイントのシーケンスを表します。
- 文字列の長さ(
len(s)
)はバイト数を返します。 - 文字列をイテレートすると、各ルーン(Unicodeコードポイント)が取得されます。
-
バイトスライス (
[]byte
):- バイトの可変長シーケンスです。
- Goでは、文字列とバイトスライスは密接に関連しており、相互に変換可能です。
[]byte("hello")
のように文字列リテラルからバイトスライスを作成したり、string([]byte{...})
のようにバイトスライスから文字列を作成できます。
-
ルーンスライス (
[]rune
):- ルーン(
rune
型はint32
のエイリアスで、Unicodeコードポイントを表す)の可変長シーケンスです。 - 文字列をルーンスライスに変換すると、文字列内の各Unicodeコードポイントが個別の
rune
要素として格納されます。これにより、マルチバイト文字の正確な処理が可能になります。
- ルーン(
-
スライスの
nil
と空 (empty):- Goのスライスは、内部的にポインタ、長さ(
len
)、容量(cap
)の3つの要素で構成されます。 nil
スライス: スライスのゼロ値です。ポインタがnil
であり、長さと容量が0です。var s []T
のように宣言された直後のスライスはnil
です。nil
スライスは有効なスライスであり、len(s)
は0、cap(s)
も0を返します。- 空スライス: 長さが0のスライスです。
make([]T, 0)
や[]T{}
のように作成されます。ポインタはnil
ではない場合が多いですが、長さと容量は0です。 - Goの多くの操作において、
nil
スライスと空スライスは同じように振る舞います(例:range
ループ、append
)。しかし、s == nil
のような比較では明確に区別されます。この区別が、本コミットの修正の核心部分です。
- Goのスライスは、内部的にポインタ、長さ(
-
型変換 (Conversions):
- Goでは、異なる型間で値を変換することができます。
T(x)
の形式で記述され、x
の値を型T
に変換します。- 文字列とスライスの変換は、Go言語の仕様で厳密に定義されています。
これらの概念を理解することで、なぜ「空の文字列から変換されたスライスがnil
であるべきか、それともnil
ではない空のスライスであるべきか」という点が重要になるのか、そしてなぜ仕様を実装に合わせたのかが明確になります。
技術的詳細
このコミットの技術的な詳細は、Go言語の型変換ルール、特に文字列とスライス間の変換における「nil
スライス」と「空スライス」の扱いに関するものです。
Go言語の仕様では、特定の変換において結果がnil
スライスになるべきか、それともnil
ではない空スライスになるべきかについて、厳密な定義があります。しかし、実際のコンパイラの実装では、一部のケースで仕様と異なる挙動をしていました。
問題点:
-
空の文字列からバイトスライス/ルーンスライスへの変換:
- 旧仕様:
string("")
を[]byte
や[]rune
に変換すると、結果はnil
スライス(例:[]byte(nil)
、[]rune(nil)
)になるべきだとされていました。 - 実際のコンパイラ:
string("")
を[]byte
や[]rune
に変換すると、結果はnil
ではない空のスライス(例:[]byte{}
、[]rune{}
)を返していました。 - この不一致は、
if s == nil
のようなチェックを行うコードにおいて、予期せぬ結果を招く可能性がありました。コンパイラの挙動は、空の文字列が空のバイト列やルーン列に対応するという直感的な理解と一致しており、nil
であるという追加の要件は不自然であると判断されました。
- 旧仕様:
-
nil
のバイトスライス/ルーンスライスから文字列への変換:- 旧仕様:
nil
の[]byte
や[]rune
を文字列に変換する際に、結果が空文字列になることについて、過剰な記述がありました。 - 実際の挙動: Goのスライスは、
nil
であっても長さが0であれば、多くの文脈で空のスライスと同様に扱われます。例えば、len(nil_slice)
は0を返し、for range nil_slice
は何もイテレートしません。したがって、nil
スライスを文字列に変換した場合に空文字列になるのは、Goのスライスの基本的な振る舞いから自然に導かれる結果であり、特別な記述は不要でした。
- 旧仕様:
修正内容:
-
空の文字列からスライスへの変換の仕様変更:
doc/go_spec.html
の記述が修正され、空の文字列を[]byte
や[]rune
に変換した場合、結果がnil
ではない空のスライス([]byte{}
や[]rune{}
)になるように変更されました。これにより、仕様が実際のコンパイラの挙動と一致するようになりました。- 具体的には、
If the string is empty, the result is []byte(nil).
やIf the string is empty, the result is []rune(nil).
といった記述が削除されました。 - 新しい例として
[]byte("") // []byte{}
や[]rune("") // []rune{}
が追加され、挙動が明確化されました。
-
nil
スライスから文字列への変換の記述の簡素化:doc/go_spec.html
から、nil
の[]byte
や[]rune
を文字列に変換した場合に空文字列になるという記述が削除されました。これは、nil
スライスが既に空のスライスと同様に振る舞うため、この変換結果は自明であるという判断に基づいています。- 代わりに、
string([]byte(nil)) // ""
やstring([]rune(nil)) // ""
といった例が追加され、挙動が示されています。これにより、冗長な説明を避けつつ、具体的な挙動を理解しやすくなりました。
-
テストケースの追加:
test/fixedbugs/issue5704.go
という新しいテストファイルが追加されました。このテストは、空の文字列をバイトスライスやルーンスライスに変換した際に、結果が空であり、かつnil
ではないことを検証します。これにより、将来的にコンパイラの挙動が仕様から逸脱しないように保証されます。
この変更は、Go言語の仕様の正確性と一貫性を向上させ、開発者がより予測可能なコードを書けるようにするためのものです。
コアとなるコードの変更箇所
このコミットによる主要なコードの変更は、Go言語の仕様書を記述したHTMLファイル(doc/go_spec.html
)と、新しいテストケースファイル(test/fixedbugs/issue5704.go
)の2箇所です。
-
doc/go_spec.html
:- 変更前:
- バイトスライスへの変換:
If the slice value is <code>nil</code>, the result is the empty string.
(これは文字列からスライスへの変換ではなく、スライスから文字列への変換に関する記述の誤り。実際には、空文字列からバイトスライスへの変換でnil
スライスを返すという記述があった。) - ルーンスライスへの変換:
If the slice value is <code>nil</code>, the result is the empty string.
(上記と同様の誤り。) - 文字列からバイトスライスへの変換:
If the string is empty, the result is <code>[]byte(nil)</code>.
- 文字列からルーンスライスへの変換:
If the string is empty, the result is <code>[]rune(nil)</code>.
- バイトスライスへの変換:
- 変更後:
- バイトスライスから文字列への変換に関する記述から、
nil
スライスの場合に空文字列になるという冗長な説明を削除。 - ルーンスライスから文字列への変換に関する記述から、
nil
スライスの場合に空文字列になるという冗長な説明を削除。 - 文字列からバイトスライスへの変換に関する記述から、空文字列の場合に
[]byte(nil)
を返すという記述を削除。 - 文字列からルーンスライスへの変換に関する記述から、空文字列の場合に
[]rune(nil)
を返すという記述を削除。 - 代わりに、以下の新しい例が追加されました:
string([]byte{})
->""
string([]byte(nil))
->""
string([]rune{})
->""
string([]rune(nil))
->""
[]byte("")
->[]byte{}
[]rune("")
->[]rune{}
- バイトスライスから文字列への変換に関する記述から、
- 変更前:
-
test/fixedbugs/issue5704.go
:- このファイルは新規追加されました。
- 空の文字列を
[]byte
や[]rune
に変換した結果が、長さ0であり、かつnil
ではないことを検証するテスト関数checkBytes
とcheckRunes
が含まれています。 main
関数内で、[]byte("")
,[]byte(mystring(""))
,mybytes("")
,mybytes(mystring(""))
および[]rune("")
,[]rune(mystring(""))
,myrunes("")
,myrunes(mystring(""))
の各変換結果が期待通りであることを確認しています。
これらの変更により、Go言語の仕様が実際のコンパイラの挙動と一致し、より明確で正確なものになりました。
コアとなるコードの解説
このコミットのコアとなるコードの変更は、Go言語の仕様書(doc/go_spec.html
)における文字列とスライス間の変換に関する記述の修正と、その挙動を検証するためのテストコード(test/fixedbugs/issue5704.go
)の追加です。
doc/go_spec.html
の変更点
仕様書の変更は、主に以下の4つのセクションに影響を与えています。
-
Converting a slice of bytes to a string type:
- 変更前:
If the slice value is <code>nil</code>, the result is the empty string.
- 変更後: この記述が削除されました。
- 解説:
nil
のバイトスライスが空文字列に変換されるのは、Goのスライスの基本的な振る舞い(nil
スライスも長さ0)から自明であるため、冗長な説明が削除されました。代わりに、string([]byte{})
とstring([]byte(nil))
が両方とも空文字列になる例が追加され、具体的な挙動が示されています。
- 変更前:
-
Converting a slice of runes to a string type:
- 変更前:
If the slice value is <code>nil</code>, the result is the empty string.
- 変更後: この記述が削除されました。
- 解説: バイトスライスの場合と同様に、
nil
のルーンスライスが空文字列に変換されるのは自明であるため、冗長な説明が削除されました。string([]rune{})
とstring([]rune(nil))
が両方とも空文字列になる例が追加されています。
- 変更前:
-
Converting a value of a string type to a slice of bytes type:
- 変更前:
If the string is empty, the result is <code>[]byte(nil)</code>.
- 変更後: この記述が削除されました。
- 解説: これが本コミットの主要な修正点です。旧仕様では空文字列をバイトスライスに変換すると
nil
スライスになるとしていましたが、実際のコンパイラはnil
ではない空のスライスを返していました。この修正により、仕様が実装に合わせられ、[]byte("")
が[]byte{}
(nil
ではない空スライス)を返すことが明確になりました。
- 変更前:
-
Converting a value of a string type to a slice of runes type:
- 変更前:
If the string is empty, the result is <code>[]rune(nil)</code>.
- 変更後: この記述が削除されました。
- 解説: バイトスライスの場合と同様に、空文字列をルーンスライスに変換すると
nil
ではない空のスライス([]rune{}
)を返すように仕様が変更されました。
- 変更前:
これらの変更は、Go言語の型変換のセマンティクスをより正確に反映し、開発者が混乱することなくコードを書けるようにすることを目的としています。
test/fixedbugs/issue5704.go
の解説
このテストファイルは、Go言語のテストフレームワークの一部として、特定のバグ修正や仕様変更の挙動を検証するために追加されました。
package main
: 実行可能なテストであることを示します。type ( mystring string; mybytes []byte; myrunes []rune )
: 基本型(string
,[]byte
,[]rune
)のエイリアスを定義しています。これは、基底型だけでなく、そのエイリアス型に対しても変換の挙動が正しく適用されることを確認するためです。func checkBytes(s []byte, arg string)
:len(s) != 0
のチェック: 変換結果のスライスの長さが0であることを確認します。空文字列からの変換なので、結果のスライスは空であるべきです。s == nil
のチェック: 変換結果のスライスがnil
ではないことを確認します。これがこのコミットの核心部分で、空文字列からの変換結果がnil
ではない空スライスであることを保証します。- いずれかの条件が満たされない場合、
panic
を発生させてテストを失敗させます。
func checkRunes(s []rune, arg string)
:checkBytes
と同様に、ルーンスライスに対する同様の検証を行います。
func main()
:checkBytes
とcheckRunes
を呼び出し、様々な形式の空文字列(リテラル、エイリアス型)からの変換結果を検証します。- 例:
checkBytes([]byte(""),
[]byte(""))
は、空文字列リテラルをバイトスライスに変換した結果が、長さ0でnil
ではないことを確認します。
このテストの追加により、Goコンパイラが将来的にこの仕様に準拠し続けることが保証されます。これは、Go言語の「テスト駆動開発」的なアプローチの一例であり、仕様の変更がコードベースで検証可能であることを示しています。
関連リンク
- Go言語の仕様書: https://go.dev/ref/spec (このコミットで修正された内容が反映されています)
- Go言語のIssue 5704: https://go.dev/issue/5704
参考にした情報源リンク
- Go言語のIssue 5704: https://go.dev/issue/5704
- Go言語の変更リスト (CL) 10440045: https://golang.org/cl/10440045
- Go言語の公式ドキュメント(文字列、スライス、型変換に関するセクション)