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

[インデックス 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.ConvertType.ConvertibleTo などの新しいメソッドが追加され、より強力な型操作が可能になりました。また、Go 1.2では encoding/xml が属性のマーシャリングや、新しい encoding パッケージで定義された汎用エンコーディングインターフェース(MarshalerUnmarshaler など)をサポートするようになりました。

これらの 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.ValueKind()reflect.Ptr を返します。ポインタが nil の場合、IsNil() メソッドが true を返します。ポインタが nil の場合、reflect.New(pv.Type().Elem()) を使って新しい要素のインスタンスを作成し、Set メソッドでそのポインタに設定することで、ポインタが指す実体を初期化できます。その後、Elem() メソッドを使ってポインタが指す実際の値(要素)を取得し、その値に対して操作を行います。

技術的詳細

このコミットの主要な変更点は、encoding/xml/read.go 内の copyValue 関数における値の変換ロジックの簡素化です。

変更前は、copyValue 関数内で整数、符号なし整数、浮動小数点数の変換のために、それぞれ getInt64getUint64getFloat64 という3つのローカルヘルパー関数が定義されていました。これらのヘルパー関数は、strconv パッケージの ParseIntParseUintParseFloat を呼び出し、エラーチェックを行っていました。

変更後、これらのローカルヘルパー関数は削除されました。代わりに、strconv パッケージの変換関数が switch dst.Kind() ブロック内で直接呼び出されるようになりました。

特に注目すべきは、strconv.ParseIntstrconv.ParseUintstrconv.ParseFloatbitSize 引数に dst.Type().Bits() が直接渡されるようになった点です。

  • 変更前は、getInt64getUint64 は常に 64bitSize として渡していました。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 にコピーする役割を担っています。

  1. ポインタの処理の簡素化:

    • 変更前は 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() によって、ポインタが指す実際の値(要素)を取得し、以降の処理はその要素に対して行われます。
  2. 型変換ヘルパー関数の削除と直接呼び出し:

    • 変更前は、getInt64getUint64getFloat64 という3つのローカルヘルパー関数が定義され、それぞれ strconv.ParseIntstrconv.ParseUintstrconv.ParseFloat を呼び出していました。これらのヘルパー関数は、冗長であり、strconv の機能を直接利用できるため削除されました。
    • switch dst.Kind() ブロック内で、各数値型(reflect.Int, reflect.Uint, reflect.Float32, reflect.Float64 など)に対応する strconv の関数が直接呼び出されるようになりました。
    • 特に重要なのは、strconv.ParseIntstrconv.ParseUintstrconv.ParseFloatbitSize 引数に dst.Type().Bits() が渡されるようになった点です。これにより、変換先のGoの型のビットサイズ(例: int8 なら8、float32 なら32)が正確に strconv 関数に伝えられ、より適切な型変換とエラーチェックが行われます。
    • 変換が成功した場合、dst.SetInt(itmp)dst.SetUint(utmp)dst.SetFloat(ftmp) を使って、変換された値を dst に設定します。エラーが発生した場合は、そのエラーを返します。
  3. その他の型の処理:

    • reflect.Boolreflect.Stringreflect.Slicereflect.Struct(特に time.Time 型)の処理は、基本的なロジックは変更されていませんが、t.Set...t.Type() の部分が dst.Set...dst.Type() に変更され、一時変数 t の使用が排除されています。

この変更により、copyValue 関数はより簡潔になり、strconv パッケージの機能をより直接的かつ適切に利用するようになりました。これは、Goの reflect APIの成熟と、コードの可読性および保守性の向上に貢献しています。

関連リンク

参考にした情報源リンク