[インデックス 14957] ファイルの概要
このコミットは、src/pkg/encoding/xml/read.go
ファイルに影響を与えています。具体的には、59行の変更があり、21行が追加され、38行が削除されています。
コミット
encoding/xml: simplify copyValue
Delete various complications left over from an earlier reflect API.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7124063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9114279c6688f9c37de61af1fd77142b4ff6d7e4
元コミット内容
encoding/xml: simplify copyValue
Delete various complications left over from an earlier reflect API.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7124063
変更の背景
このコミットは、Go言語の標準ライブラリである encoding/xml
パッケージ内の copyValue
関数を簡素化することを目的としています。コミットメッセージには「以前のreflect APIから残された様々な複雑さを削除する」と明記されています。
Go言語の reflect
パッケージは、プログラムの実行時に型情報を検査し、値を動的に操作するための機能を提供します。encoding/xml
パッケージは、XMLデータをGoの構造体にアンマーシャル(デシリアライズ)する際に、この reflect
パッケージを広範に利用しています。特に、XML要素のテキストコンテンツをGoの構造体のフィールドに割り当てる際に、copyValue
のような内部ヘルパー関数が使用されます。
2013年頃のGo 1.1およびGo 1.2リリースでは、reflect
パッケージに重要な追加機能が導入され、encoding/xml
パッケージ自体も改善されました。Go 1.1では reflect
パッケージに Value.Convert
、Type.ConvertibleTo
などの新しいメソッドが追加され、より強力な型操作が可能になりました。また、Go 1.2では encoding/xml
が属性のマーシャリングや、新しい encoding
パッケージで定義された汎用エンコーディングインターフェース(Marshaler
、Unmarshaler
など)をサポートするようになりました。
これらの reflect
APIの進化と encoding/xml
パッケージ自体の改善により、以前のバージョンで必要とされていた一部の複雑な処理が不要になったか、より簡潔な方法で実現可能になったと考えられます。このコミットは、そのようなAPIの進化に合わせて、copyValue
関数内の冗長なコードや古い reflect
APIに依存する部分を削除し、コードベースをクリーンアップし、保守性を向上させるためのものです。
前提知識の解説
Go言語の encoding/xml
パッケージ
encoding/xml
パッケージは、GoのプログラムでXMLデータをエンコード(マーシャル)およびデコード(アンマーシャル)するための機能を提供します。XMLドキュメントとGoの構造体(struct)の間でデータを変換する際に使用されます。特に xml.Unmarshal
関数は、XMLデータをGoの構造体に読み込む際に、XML要素や属性の値を対応する構造体フィールドにマッピングします。このマッピング処理の内部で、reflect
パッケージが利用され、動的に型を判断し、値を設定します。
Go言語の reflect
パッケージ
reflect
パッケージは、Goのプログラムが自身の構造を検査し、実行時にオブジェクトの型や値を操作するための機能を提供します。これにより、コンパイル時には不明な型や構造を持つデータを扱う汎用的なコードを書くことが可能になります。
reflect.Value
はGoの変数の値を表し、reflect.Type
はGoの変数の型を表します。reflect.Value
のメソッド(例: SetInt
, SetUint
, SetFloat
, SetString
, SetBool
, SetBytes
)を使って、動的に値を設定することができます。また、Kind()
メソッドで値の基本的な種類(例: reflect.Int
, reflect.String
, reflect.Slice
)を取得できます。
strconv
パッケージ
strconv
パッケージは、文字列と基本的なデータ型(整数、浮動小数点数、ブール値など)の間で変換を行うための機能を提供します。
strconv.ParseInt(s string, base int, bitSize int)
: 文字列s
を指定された基数(base
)とビットサイズ(bitSize
)の符号付き整数に変換します。strconv.ParseUint(s string, base int, bitSize int)
: 文字列s
を指定された基数(base
)とビットサイズ(``bitSize`)の符号なし整数に変換します。strconv.ParseFloat(s string, bitSize int)
: 文字列s
を指定されたビットサイズ(bitSize
)の浮動小数点数に変換します。 これらの関数は、変換に成功した場合は変換された値とnil
エラーを返し、失敗した場合はゼロ値とエラーを返します。
ポインタの扱い (reflect.Ptr
)
Goでは、ポインタはメモリ上のアドレスを指します。reflect
パッケージでポインタを扱う場合、reflect.Value
の Kind()
が reflect.Ptr
を返します。ポインタが nil
の場合、IsNil()
メソッドが true
を返します。ポインタが nil
の場合、reflect.New(pv.Type().Elem())
を使って新しい要素のインスタンスを作成し、Set
メソッドでそのポインタに設定することで、ポインタが指す実体を初期化できます。その後、Elem()
メソッドを使ってポインタが指す実際の値(要素)を取得し、その値に対して操作を行います。
技術的詳細
このコミットの主要な変更点は、encoding/xml/read.go
内の copyValue
関数における値の変換ロジックの簡素化です。
変更前は、copyValue
関数内で整数、符号なし整数、浮動小数点数の変換のために、それぞれ getInt64
、getUint64
、getFloat64
という3つのローカルヘルパー関数が定義されていました。これらのヘルパー関数は、strconv
パッケージの ParseInt
、ParseUint
、ParseFloat
を呼び出し、エラーチェックを行っていました。
変更後、これらのローカルヘルパー関数は削除されました。代わりに、strconv
パッケージの変換関数が switch dst.Kind()
ブロック内で直接呼び出されるようになりました。
特に注目すべきは、strconv.ParseInt
、strconv.ParseUint
、strconv.ParseFloat
の bitSize
引数に dst.Type().Bits()
が直接渡されるようになった点です。
- 変更前は、
getInt64
とgetUint64
は常に64
をbitSize
として渡していました。getFloat64
も同様に64
を渡していました。 - 変更後、
dst.Type().Bits()
を使用することで、変換対象のGoの型(例:int8
,int16
,int32
,int64
,float32
,float64
など)の実際のビットサイズに基づいてstrconv
関数が呼び出されるようになります。これにより、より正確な型変換と、潜在的なオーバーフローチェックがstrconv
側で適切に行われるようになります。
また、ポインタの処理も簡素化されています。
- 変更前:
if pv := dst; pv.Kind() == reflect.Ptr { ... dst = pv.Elem() }
- 変更後:
if dst.Kind() == reflect.Ptr { ... dst = dst.Elem() }
pv
という一時変数を導入せず、直接dst
を操作することで、コードがより直接的で読みやすくなっています。機能的な違いはありません。
switch
文の条件も t := dst; t.Kind()
から dst.Kind()
に変更され、冗長な一時変数 t
の使用が排除されています。
全体として、この変更は、Goの reflect
パッケージと strconv
パッケージの進化により、以前は必要だった冗長なコードや複雑なロジックが不要になったことを示しています。これにより、copyValue
関数はより簡潔で、効率的で、保守しやすいコードになっています。
コアとなるコードの変更箇所
diff --git a/src/pkg/encoding/xml/read.go b/src/pkg/encoding/xml/read.go
index 6bc23e1226..344ab514e3 100644
--- a/src/pkg/encoding/xml/read.go
+++ b/src/pkg/encoding/xml/read.go
@@ -374,75 +374,58 @@ Loop:
}
func copyValue(dst reflect.Value, src []byte) (err error) {
- // Helper functions for integer and unsigned integer conversions
- var itmp int64
- getInt64 := func() bool {
- itmp, err = strconv.ParseInt(string(src), 10, 64)
- // TODO: should check sizes
- return err == nil
- }
- var utmp uint64
- getUint64 := func() bool {
- utmp, err = strconv.ParseUint(string(src), 10, 64)
- // TODO: check for overflow?
- return err == nil
- }
- var ftmp float64
- getFloat64 := func() bool {
- ftmp, err = strconv.ParseFloat(string(src), 64)
- // TODO: check for overflow?
- return err == nil
- }
-
- if pv := dst; pv.Kind() == reflect.Ptr {
- if pv.IsNil() {
- pv.Set(reflect.New(pv.Type().Elem()))
- }
- dst = pv.Elem()
- }
-
- // Save accumulated data.
- switch t := dst; t.Kind() {
+ if dst.Kind() == reflect.Ptr {
+ if dst.IsNil() {
+ dst.Set(reflect.New(dst.Type().Elem()))
+ }
+ dst = dst.Elem()
+ }
+
+ // Save accumulated data.
+ switch dst.Kind() {
case reflect.Invalid:
- // Probably a comment.
+ // Probably a commendst.
default:
- return errors.New(\"cannot happen: unknown type \" + t.Type().String())\n
+ return errors.New(\"cannot happen: unknown type \" + dst.Type().String())\n
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- if !getInt64() {
+ itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())
+ if err != nil {
return err
}
- t.SetInt(itmp)
+ dst.SetInt(itmp)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- if !getUint64() {
+ utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits())
+ if err != nil {
return err
}
- t.SetUint(utmp)
+ dst.SetUint(utmp)
case reflect.Float32, reflect.Float64:
- if !getFloat64() {
+ ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits())
+ if err != nil {
return err
}
- t.SetFloat(ftmp)
+ dst.SetFloat(ftmp)
case reflect.Bool:
value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
if err != nil {
return err
}
- t.SetBool(value)
+ dst.SetBool(value)
case reflect.String:
- t.SetString(string(src))
+ dst.SetString(string(src))
case reflect.Slice:
if len(src) == 0 {
// non-nil to flag presence
src = []byte{}\n
}
- t.SetBytes(src)
+ dst.SetBytes(src)
case reflect.Struct:
- if t.Type() == timeType {
+ if dst.Type() == timeType {
tv, err := time.Parse(time.RFC3339, string(src))\n
if err != nil {
return err
}
- t.Set(reflect.ValueOf(tv))\n
+ dst.Set(reflect.ValueOf(tv))\n
}
}\n
return nil
コアとなるコードの解説
copyValue
関数は、XMLから読み取ったバイトスライス src
の内容を、reflect.Value
型の dst
にコピーする役割を担っています。
-
ポインタの処理の簡素化:
- 変更前は
if pv := dst; pv.Kind() == reflect.Ptr { ... }
のように一時変数pv
を導入していましたが、変更後はif dst.Kind() == reflect.Ptr { ... }
と直接dst
を使用するように変更されました。これにより、コードがより直接的になりました。 - ポインタが
nil
の場合 (dst.IsNil()
)、reflect.New(dst.Type().Elem())
を使って新しい要素のインスタンスを作成し、dst.Set()
でそのポインタに設定します。これにより、ポインタが指す実体が初期化されます。 - その後、
dst = dst.Elem()
によって、ポインタが指す実際の値(要素)を取得し、以降の処理はその要素に対して行われます。
- 変更前は
-
型変換ヘルパー関数の削除と直接呼び出し:
- 変更前は、
getInt64
、getUint64
、getFloat64
という3つのローカルヘルパー関数が定義され、それぞれstrconv.ParseInt
、strconv.ParseUint
、strconv.ParseFloat
を呼び出していました。これらのヘルパー関数は、冗長であり、strconv
の機能を直接利用できるため削除されました。 switch dst.Kind()
ブロック内で、各数値型(reflect.Int
,reflect.Uint
,reflect.Float32
,reflect.Float64
など)に対応するstrconv
の関数が直接呼び出されるようになりました。- 特に重要なのは、
strconv.ParseInt
、strconv.ParseUint
、strconv.ParseFloat
のbitSize
引数にdst.Type().Bits()
が渡されるようになった点です。これにより、変換先のGoの型のビットサイズ(例:int8
なら8、float32
なら32)が正確にstrconv
関数に伝えられ、より適切な型変換とエラーチェックが行われます。 - 変換が成功した場合、
dst.SetInt(itmp)
、dst.SetUint(utmp)
、dst.SetFloat(ftmp)
を使って、変換された値をdst
に設定します。エラーが発生した場合は、そのエラーを返します。
- 変更前は、
-
その他の型の処理:
reflect.Bool
、reflect.String
、reflect.Slice
、reflect.Struct
(特にtime.Time
型)の処理は、基本的なロジックは変更されていませんが、t.Set...
やt.Type()
の部分がdst.Set...
やdst.Type()
に変更され、一時変数t
の使用が排除されています。
この変更により、copyValue
関数はより簡潔になり、strconv
パッケージの機能をより直接的かつ適切に利用するようになりました。これは、Goの reflect
APIの成熟と、コードの可読性および保守性の向上に貢献しています。
関連リンク
参考にした情報源リンク
- Go 1.1 Release Notes:
reflect
package additions: https://go.dev/doc/go1.1#reflect - Go 1.2 Release Notes:
encoding/xml
improvements: https://go.dev/doc/go1.2#encoding/xml - The Laws of Reflection (Go Blog): https://go.dev/blog/laws-of-reflection
- strconv package documentation: https://pkg.go.dev/strconv
- reflect package documentation: https://pkg.go.dev/reflect