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

[インデックス 10482] ファイルの概要

このコミットは、Go言語の仕様とコンパイラにおける型変換規則の拡張に関するものです。具体的には、string型と、[]byteまたは[]runeの基底型を持つ名前付き型(例: type Tbyte []byte)との間の直接的な変換を許可するように変更されました。これにより、Go言語の柔軟性と表現力が向上し、特定のコーディングパターンがより簡潔に記述できるようになりました。

変更されたファイルは以下の通りです。

  • doc/go_spec.html: Go言語の仕様書。型変換に関する記述が更新されました。
  • src/cmd/gc/subr.c: Goコンパイラのサブモジュール。型変換のロジックが修正されました。
  • src/pkg/encoding/xml/xml_test.go: encoding/xmlパッケージのテストファイル。型変換の変更に合わせてテストコードが更新されました。
  • src/pkg/net/http/sniff.go: net/httpパッケージのファイルスニッフィング関連ファイル。型変換の変更に合わせてコードが更新されました。
  • src/pkg/net/mail/message.go: net/mailパッケージのメールメッセージ処理関連ファイル。型変換の変更に合わせてコードが更新されました。
  • test/convert1.go: 新規追加されたテストファイル。新しい型変換規則のテストケースが含まれています。
  • test/convlit.go: 既存のテストファイル。型変換の変更に合わせてテストコードが更新されました。
  • test/named1.go: 既存のテストファイル。型変換の変更に合わせてテストコードが更新されました。

コミット

commit 6e3e3809231c71fc30b6d0cdcb1f60c5e6e816ef
Author: Russ Cox <rsc@golang.org>
Date:   Tue Nov 22 12:30:02 2011 -0500

    allow direct conversion between string and named []byte, []rune

    The allowed conversions before and after are:
            type Tstring string
            type Tbyte []byte
            type Trune []rune

            string <-> string  // ok
            string <-> []byte  // ok
            string <-> []rune // ok
            string <-> Tstring // ok
            string <-> Tbyte // was illegal, now ok
            string <-> Trune // was illegal, now ok

            Tstring <-> string  // ok
            Tstring <-> []byte  // ok
            Tstring <-> []rune // ok
            Tstring <-> Tstring // ok
            Tstring <-> Tbyte // was illegal, now ok
            Tstring <-> Trune // was illegal, now ok

    Update spec, compiler, tests.  Use in a few packages.

    We agreed on this a few months ago but never implemented it.

    Fixes #1707.

    R=golang-dev, gri, r
    CC=golang-dev
    https://golang.org/cl/5421057

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6e3e3809231c71fc30b6d0cdcb1f60c5e6e816ef

元コミット内容

allow direct conversion between string and named []byte, []rune

The allowed conversions before and after are:
        type Tstring string
        type Tbyte []byte
        type Trune []rune

        string <-> string  // ok
        string <-> []byte  // ok
        string <-> []rune // ok
        string <-> Tstring // ok
        string <-> Tbyte // was illegal, now ok
        string <-> Trune // was illegal, now ok

        Tstring <-> string  // ok
        Tstring <-> []byte  // ok
        Tstring <-> []rune // ok
        Tstring <-> Tstring // ok
        Tstring <-> Tbyte // was illegal, now ok
        Tstring <-> Trune // was illegal, now ok

Update spec, compiler, tests.  Use in a few packages.

We agreed on this a few months ago but never implemented it.

Fixes #1707.

R=golang-dev, gri, r
CC=golang-dev
https://golang.org/cl/5421057

変更の背景

このコミットは、Go言語の型変換規則における長年の課題を解決するために導入されました。以前のGo言語では、string型と、[]byteまたは[]runeの基底型を持つ「名前付き型」(例: type MyBytes []byte)との間の直接的な型変換が許可されていませんでした。これは、Goの型システムが厳密であることに起因しますが、実用上不便なケースも存在しました。

例えば、[]byte型のデータを扱うライブラリが、そのデータをラップする独自の型(type MyData []byteなど)を定義している場合、stringからMyDataへの変換やその逆の変換を行うには、一度[]byteに変換してから再度MyDataに変換するという冗長なステップが必要でした。

コミットメッセージにある「We agreed on this a few months ago but never implemented it. (数ヶ月前にこれに合意したが、実装されていなかった)」という記述は、この機能の必要性が以前から認識されており、Goコミュニティ内で合意が形成されていたことを示唆しています。この変更は、Go言語の使いやすさと表現力を向上させ、開発者がより自然な形で型変換を行えるようにすることを目的としています。また、Fixes #1707という記述から、この変更が特定のGo issue(問題報告)に対応するものであることがわかります。Goのコードレビューシステム(Gerrit)のリンク https://golang.org/cl/5421057 も提供されており、この変更に関する議論の経緯を追うことができます。

前提知識の解説

このコミットの理解には、Go言語の以下の基本的な概念が不可欠です。

  1. Goの型システム: Goは静的型付け言語であり、変数は特定の型を持ちます。型はデータの種類と、そのデータに対して実行できる操作を定義します。Goの型システムは厳密であり、異なる型間の暗黙的な変換はほとんど許可されません。
  2. 基底型と名前付き型:
    • 基底型 (Underlying Type): Goの組み込み型(int, string, []byteなど)や、構造体、配列、スライス、マップ、チャネル、関数などの複合型を指します。
    • 名前付き型 (Named Type): type MyType UnderlyingTypeのように、既存の型に新しい名前を付けて定義した型です。例えば、type MyString stringと定義した場合、MyStringstringを基底型とする新しい名前付き型になります。名前付き型は、基底型と同じ操作をサポートしますが、異なる型として扱われます。つまり、MyString型の変数をstring型の変数に直接代入することはできません(型変換が必要です)。
  3. string: Goのstring型は、不変のバイトスライスとして実装されており、UTF-8エンコードされたテキストを表します。文字列リテラルはダブルクォート("")で囲みます。
  4. []byte型 (バイトスライス): 可変長のバイトのシーケンスです。バイナリデータや、UTF-8エンコードされた文字列のバイト表現を扱う際によく使用されます。
  5. []rune型 (ルーンスライス): 可変長のUnicodeコードポイント(rune型はint32のエイリアス)のシーケンスです。Goでは、Unicode文字を扱う際にruneを使用します。string[]runeに変換すると、文字列内の各Unicode文字が個別のruneとして表現されます。
  6. 型変換 (Type Conversion): Goでは、異なる型間で値を変換するには、明示的な型変換(T(v)の形式)が必要です。例えば、int型の変数ifloat64型に変換するにはfloat64(i)と記述します。以前は、string[]byte[]runeの間では直接変換が可能でしたが、これらの基底型を持つ名前付き型との間では直接変換ができませんでした。

このコミットは、特に「名前付き型」と「基底型」の間の型変換の厳密さを緩和し、特定の組み合わせにおいて直接変換を許可することで、Go言語の型システムの柔軟性を高めるものです。

技術的詳細

このコミットの核心は、Go言語の型変換規則を拡張し、string型と、[]byteまたは[]runeを基底型とする名前付き型との間の直接変換を許可することです。

以前のGo言語の仕様では、以下の変換は許可されていました。

  • string <-> string (当然)
  • string <-> []byte (組み込み型間の変換)
  • string <-> []rune (組み込み型間の変換)
  • string <-> Tstring (基底型がstringの名前付き型との変換)

しかし、以下の変換は違法でした。

  • string <-> Tbyte (ここでTbytetype Tbyte []byteのように定義された名前付き型)
  • string <-> Trune (ここでTrunetype Trune []runeのように定義された名前付き型)

このコミットにより、上記の「違法」だった変換が合法となりました。同様に、Tstringstringを基底型とする名前付き型)とTbyteTruneとの間の変換も合法化されました。

具体的には、以下の変換が可能になります。

  • string <-> Tbyte (例: string(myTbyteVar), Tbyte(myStringVar))
  • string <-> Trune (例: string(myTruneVar), Trune(myStringVar))
  • Tstring <-> Tbyte (例: Tstring(myTbyteVar), Tbyte(myTstringVar))
  • Tstring <-> Trune (例: Tstring(myTruneVar), Trune(myTstringVar))

この変更は、Goコンパイラ(src/cmd/gc/subr.c)における型変換のチェックロジックを修正することで実現されています。特に、convertop関数内で、スライス型が名前付き型であるかどうかのチェックを緩和し、その基底型がbyteまたはruneである場合にstringとの変換を許可するように変更されました。

また、Go言語の公式仕様書(doc/go_spec.html)も更新され、この新しい変換規則が明示的に記載されました。これにより、Go言語の動作が仕様と一致し、開発者が新しい変換を利用できるようになります。

この変更のメリットは、コードの簡潔性と可読性の向上です。例えば、特定のバイトスライスを扱うカスタム型がある場合、以前はstring(myCustomBytes[:])のようにスライス全体を明示的に指定する必要がありましたが、今後はstring(myCustomBytes)のように直接変換できるようになります。これは、特に既存のライブラリやフレームワークでカスタム型が多用されている場合に、コードの記述量を減らし、エラーの可能性を低減する効果があります。

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

このコミットにおけるコアとなるコードの変更は、主にGoコンパイラの型変換処理を司るsrc/cmd/gc/subr.cファイルと、Go言語の仕様を記述したdoc/go_spec.htmlファイルに見られます。

doc/go_spec.html の変更

Go言語の仕様書において、string[]byte[]rune間の変換に関する記述が更新されました。特に、名前付きスライス型との変換例が追加されています。

変更前:

<li>
Converting a value of type <code>[]byte</code> to a string type yields
...
</li>

<li>
Converting a value of type <code>[]rune</code> to a string type yields
...
</li>

<li>
Converting a value of a string type to <code>[]byte</code> (or <code>[]uint8</code>)
...
</li>

<li>
Converting a value of a string type to <code>[]rune</code> yields a
-slice containing the individual Unicode code points of the string.
...
</li>

変更後:

<li>
Converting a slice of bytes to a string type yields
...
<pre>
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"

type MyBytes []byte
string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})  // "hellø"
</pre>
</li>

<li>
Converting a slice of runes to a string type yields
...
<pre>
string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"

type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"
</pre>
</li>

<li>
Converting a value of a string type to a slice of bytes type
...
<pre>
[]byte("hellø")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
MyBytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
</pre>
</li>

<li>
Converting a value of a string type to a slice of runes type
yields a slice containing the individual Unicode code points of the string.
...
<pre>
[]rune(MyString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
MyRunes("白鵬翔")           // []rune{0x767d, 0x9d6c, 0x7fd4}
</pre>
</li>

これらの変更は、名前付きスライス型(MyBytes, MyRunes)とstring型との間の直接変換が合法になったことを明確に示しています。

src/cmd/gc/subr.c の変更

Goコンパイラの型変換ロジックを定義するconvertop関数が修正されました。この関数は、ある型から別の型への変換が可能かどうか、またその変換がどのような操作(オペレーション)に対応するかを決定します。

変更前:

	if(isslice(src) && src->sym == nil && dst->etype == TSTRING) {
		if(eqtype(src->type, bytetype))
			return OARRAYBYTESTR;
		if(eqtype(src->type, runetype))
			return OARRAYRUNESTR;
	}

	// 7. src is a string and dst is []byte or []rune.
	// String to slice.
	if(src->etype == TSTRING && isslice(dst) && dst->sym == nil) {
		if(eqtype(dst->type, bytetype))
			return OSTRARRAYBYTE;
		if(eqtype(dst->type, runetype))
			return OSTRARRAYRUNE;
	}

変更後:

	if(isslice(src) && dst->etype == TSTRING) {
		if(src->type->etype == bytetype->etype)
			return OARRAYBYTESTR;
		if(src->type->etype == runetype->etype)
			return OARRAYRUNESTR;
	}

	// 7. src is a string and dst is []byte or []rune.
	// String to slice.
	if(src->etype == TSTRING && isslice(dst)) {
		if(dst->type->etype == bytetype->etype)
			return OSTRARRAYBYTE;
		if(dst->type->etype == runetype->etype)
			return OSTRARRAYRUNE;
	}

この変更のポイントは、src->sym == nil および dst->sym == nil という条件が削除されたことです。

  • isslice(src): srcがスライス型であるかをチェックします。
  • src->sym == nil: srcが名前付き型ではない(つまり、無名型である)ことをチェックします。
  • dst->sym == nil: dstが名前付き型ではない(つまり、無名型である)ことをチェックします。

これらの条件が削除されたことで、ソース型またはターゲット型が名前付きスライス型であっても、その基底型がbyteまたはruneであれば、stringとの間の変換が許可されるようになりました。また、eqtype(src->type, bytetype)のような厳密な型比較から、src->type->etype == bytetype->etypeのような基底型(要素型)の比較に変更されています。これにより、名前付きスライス型であっても、その要素型がbyteまたはruneであれば変換が許可されるようになります。

その他のファイルの変更

  • src/pkg/encoding/xml/xml_test.go, src/pkg/net/http/sniff.go, src/pkg/net/mail/message.go: これらのファイルでは、以前は[]byte("...")のように明示的にバイトスライスリテラルを作成していた箇所が、新しい変換規則を利用して"..."(文字列リテラル)を直接渡す形に修正されています。これは、コードの簡潔化と可読性向上の一例です。
  • test/convert1.go: 新規追加されたテストファイルで、stringと名前付き[]byte、名前付き[]rune、およびその他の型との間の様々な変換パターンがテストされています。これにより、新しい変換規則が正しく機能し、意図しない変換が許可されないことが検証されます。
  • test/convlit.go, test/named1.go: 既存のテストファイルが更新され、新しい変換規則が反映されています。特にconvlit.goでは、以前はエラーとなっていたTrune("abc")Tbyte("abc")のような変換が// okとコメントされ、合法になったことが示されています。

コアとなるコードの解説

src/cmd/gc/subr.c内のconvertop関数は、Goコンパイラが型変換の合法性を判断する上で中心的な役割を担っています。この関数は、ソース型(src)とターゲット型(dst)を受け取り、その間の変換が可能であれば対応するオペレーションコードを返します。

変更された部分を再掲します。

	// 以前は src->sym == nil が条件に含まれていた
	if(isslice(src) && dst->etype == TSTRING) {
		// 以前は eqtype(src->type, bytetype) だった
		if(src->type->etype == bytetype->etype)
			return OARRAYBYTESTR; // []byte から string への変換
		// 以前は eqtype(src->type, runetype) だった
		if(src->type->etype == runetype->etype)
			return OARRAYRUNESTR; // []rune から string への変換
	}

	// 7. src is a string and dst is []byte or []rune.
	// String to slice.
	// 以前は dst->sym == nil が条件に含まれていた
	if(src->etype == TSTRING && isslice(dst)) {
		// 以前は eqtype(dst->type, bytetype) だった
		if(dst->type->etype == bytetype->etype)
			return OSTRARRAYBYTE; // string から []byte への変換
		// 以前は eqtype(dst->type, runetype) だった
		if(dst->type->etype == runetype->etype)
			return OSTRARRAYRUNE; // string から []rune への変換
	}

このコードの変更は、以下の2つの主要な側面を持っています。

  1. sym == nil 条件の削除:

    • symはGoコンパイラ内部で型のシンボル情報を表すフィールドです。sym == nilは、その型が名前付き型ではなく、無名型(例: []byteそのもの)であることを意味します。
    • この条件が削除されたことで、isslice(src)srcがスライス型である)という条件さえ満たせば、それが名前付きスライス型(例: type MyBytes []byteMyBytes)であっても、stringへの変換が考慮されるようになりました。
    • 同様に、stringからスライス型への変換においても、ターゲットのスライス型が名前付き型であっても変換が考慮されます。
  2. eqtype から etype 比較への変更:

    • eqtype(type1, type2)は、2つの型が完全に等しいかどうかをチェックする関数です。
    • type->etype == othertype->etypeは、型の「要素型」(スライスの場合、その要素の型)が等しいかどうかをチェックします。
    • この変更により、例えばsrcMyBytes型(基底型が[]byte)である場合、以前はeqtype(src->type, bytetype)falseを返していたため変換が許可されませんでしたが、src->type->etype == bytetype->etypetrueを返すようになり、MyBytesの要素型がbyteであるため変換が許可されるようになりました。

これらの変更により、Goコンパイラは、stringと、[]byteまたは[]runeを基底型とする名前付きスライス型との間の直接変換を、組み込み型間の変換と同様に扱うようになりました。これにより、Go言語の型変換の柔軟性が向上し、より自然なコーディングが可能になります。

関連リンク

参考にした情報源リンク

  • Go Code Review (Gerrit) for CL 5421057: https://golang.org/cl/5421057 (Web Fetchにより取得)
  • Google Web Search for "Go issue 1707" and "golang/go issue 1707" (Go issue 1707の具体的な内容は、GerritのCL 5421057の説明から確認しました。)