Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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言語の基本的な概念を理解しておく必要があります。

  1. 文字列 (string):

    • Goの文字列は、不変のバイトスライスであり、通常はUTF-8でエンコードされたUnicodeコードポイントのシーケンスを表します。
    • 文字列の長さ(len(s))はバイト数を返します。
    • 文字列をイテレートすると、各ルーン(Unicodeコードポイント)が取得されます。
  2. バイトスライス ([]byte):

    • バイトの可変長シーケンスです。
    • Goでは、文字列とバイトスライスは密接に関連しており、相互に変換可能です。
    • []byte("hello")のように文字列リテラルからバイトスライスを作成したり、string([]byte{...})のようにバイトスライスから文字列を作成できます。
  3. ルーンスライス ([]rune):

    • ルーン(rune型はint32のエイリアスで、Unicodeコードポイントを表す)の可変長シーケンスです。
    • 文字列をルーンスライスに変換すると、文字列内の各Unicodeコードポイントが個別のrune要素として格納されます。これにより、マルチバイト文字の正確な処理が可能になります。
  4. スライスの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のような比較では明確に区別されます。この区別が、本コミットの修正の核心部分です。
  5. 型変換 (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のスライスの基本的な振る舞いから自然に導かれる結果であり、特別な記述は不要でした。

修正内容:

  1. 空の文字列からスライスへの変換の仕様変更:

    • 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{} が追加され、挙動が明確化されました。
  2. nilスライスから文字列への変換の記述の簡素化:

    • doc/go_spec.htmlから、nil[]byte[]runeを文字列に変換した場合に空文字列になるという記述が削除されました。これは、nilスライスが既に空のスライスと同様に振る舞うため、この変換結果は自明であるという判断に基づいています。
    • 代わりに、string([]byte(nil)) // ""string([]rune(nil)) // "" といった例が追加され、挙動が示されています。これにより、冗長な説明を避けつつ、具体的な挙動を理解しやすくなりました。
  3. テストケースの追加:

    • test/fixedbugs/issue5704.goという新しいテストファイルが追加されました。このテストは、空の文字列をバイトスライスやルーンスライスに変換した際に、結果が空であり、かつnilではないことを検証します。これにより、将来的にコンパイラの挙動が仕様から逸脱しないように保証されます。

この変更は、Go言語の仕様の正確性と一貫性を向上させ、開発者がより予測可能なコードを書けるようにするためのものです。

コアとなるコードの変更箇所

このコミットによる主要なコードの変更は、Go言語の仕様書を記述したHTMLファイル(doc/go_spec.html)と、新しいテストケースファイル(test/fixedbugs/issue5704.go)の2箇所です。

  1. 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{}
  2. test/fixedbugs/issue5704.go:

    • このファイルは新規追加されました。
    • 空の文字列を[]byte[]runeに変換した結果が、長さ0であり、かつnilではないことを検証するテスト関数checkBytescheckRunesが含まれています。
    • 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つのセクションに影響を与えています。

  1. 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))が両方とも空文字列になる例が追加され、具体的な挙動が示されています。
  2. 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))が両方とも空文字列になる例が追加されています。
  3. 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ではない空スライス)を返すことが明確になりました。
  4. 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():
    • checkBytescheckRunesを呼び出し、様々な形式の空文字列(リテラル、エイリアス型)からの変換結果を検証します。
    • 例: checkBytes([]byte(""), []byte("")) は、空文字列リテラルをバイトスライスに変換した結果が、長さ0でnilではないことを確認します。

このテストの追加により、Goコンパイラが将来的にこの仕様に準拠し続けることが保証されます。これは、Go言語の「テスト駆動開発」的なアプローチの一例であり、仕様の変更がコードベースで検証可能であることを示しています。

関連リンク

参考にした情報源リンク