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

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

このコミットは、Go言語の標準ライブラリ encoding/xml パッケージにおいて、匿名フィールド(anonymous fields)の扱いに関するドキュメントと例を追加・修正するものです。特に、ポインタではない匿名構造体フィールドがXMLのマーシャリング(Go構造体からXMLへの変換)およびアンマーシャリング(XMLからGo構造体への変換)でどのように扱われるかについて、その挙動を明確化し、具体的な使用例を example_test.go に追加しています。また、匿名ポインタフィールドがまだサポートされていないこと、およびその問題がIssue 3108で追跡されていることを明記しています。

コミット

commit 6c20f5c0135483da1127d15e724b2bf1608833d7
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date:   Thu Feb 23 01:35:50 2012 -0200

    encoding/xml: add example and docs for anon fields
    
    Anonymous pointer fields is not yet supported.
    The problem is documented in issue 3108.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5694043

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

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

元コミット内容

encoding/xml: add example and docs for anon fields

Anonymous pointer fields is not yet supported.
The problem is documented in issue 3108.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5694043

変更の背景

Go言語の encoding/xml パッケージは、Goの構造体とXMLデータ間の変換(マーシャリングとアンマーシャリング)を提供します。Goの構造体には「匿名フィールド(embedded fields)」という特徴があり、これはある構造体の中に別の構造体をフィールド名なしで埋め込むことができる機能です。これにより、埋め込まれた構造体のフィールドが、外側の構造体のフィールドであるかのように直接アクセスできるようになります。

しかし、encoding/xml パッケージが匿名フィールドをどのように扱うか、特にポインタではない匿名構造体と匿名ポインタ構造体の場合で、その挙動が明確ではありませんでした。このコミットの主な背景は、以下の2点に集約されます。

  1. 非ポインタ匿名構造体の挙動の明確化: encoding/xml は、ポインタではない匿名構造体フィールドを、そのフィールドが外側の構造体の一部であるかのように扱います。つまり、埋め込まれた構造体のフィールドが、XML要素として直接外側の構造体のレベルで現れることを意味します。この挙動は、Goの匿名フィールドのセマンティクスと一致しており、XMLの構造をフラット化するのに役立ちます。このコミットでは、この重要な挙動をドキュメントに明記し、具体的な例を追加することで、開発者がより正確に encoding/xml を利用できるようにしています。
  2. 匿名ポインタフィールドの未サポート問題: コミットメッセージにも明記されている通り、当時の encoding/xml パッケージは匿名ポインタフィールド(例: *Address のようにポインタとして埋め込まれた構造体)のマーシャリング/アンマーシャリングをサポートしていませんでした。この問題はGoのIssue 3108として追跡されており、このコミットは、この未サポートの現状をドキュメントに記載することで、開発者が予期せぬ挙動に遭遇するのを防ぐことを目的としています。

これらの変更は、encoding/xml パッケージの使いやすさと信頼性を向上させ、開発者がXML処理を行う際の混乱を減らすことに貢献しています。

前提知識の解説

Go言語の匿名フィールド(Embedded Fields)

Go言語の構造体は、他の構造体をフィールド名なしで埋め込むことができます。これを匿名フィールド(または埋め込みフィールド)と呼びます。匿名フィールドの主な特徴は以下の通りです。

  • フィールドの昇格: 埋め込まれた構造体のフィールドやメソッドは、外側の構造体のフィールドやメソッドであるかのように直接アクセスできます。例えば、type Person struct { Address } の場合、Person のインスタンス p から p.City のように Address 構造体の City フィールドにアクセスできます。
  • 継承の代替: Goにはクラス継承の概念はありませんが、匿名フィールドはコードの再利用と「is-a」関係のモデリングにおいて、継承に似た機能を提供します。
  • 名前の衝突: 埋め込まれた構造体と外側の構造体でフィールド名が衝突した場合、外側の構造体のフィールドが優先されます。

encoding/xml パッケージ

encoding/xml パッケージは、Goの構造体とXMLドキュメントの間でデータを変換するための標準ライブラリです。主な機能は以下の通りです。

  • マーシャリング(xml.Marshal, xml.MarshalIndent: Goの構造体のデータをXML形式のバイトスライスに変換します。構造体のフィールドにタグ(xml:"tagname,attr" など)を付けることで、XML要素名、属性、テキストコンテンツなどを制御できます。
  • アンマーシャリング(xml.Unmarshal: XML形式のバイトスライスをGoの構造体に変換します。XMLの要素や属性を、対応する構造体フィールドにマッピングします。
  • XMLタグ: 構造体フィールドのタグは、XML要素の名前、属性、処理方法(例: omitempty で空の場合に省略、,comment でコメントとして扱う)を定義するために使用されます。

XMLの構造とGo構造体のマッピング

encoding/xml は、Goの構造体フィールドをXML要素にマッピングする際に、いくつかのルールに従います。

  • フィールド名とXML要素名: デフォルトでは、Goの構造体フィールド名がXML要素名として使用されます。タグで明示的に指定することもできます。
  • 属性: xml:"name,attr" のようにタグに ,attr を追加すると、そのフィールドはXML要素の属性として扱われます。
  • テキストコンテンツ: タグなしのフィールドや、特定のタグを持つフィールドがXML要素のテキストコンテンツとして扱われることがあります。
  • ネストされた要素: 構造体の中に別の構造体がフィールドとして含まれている場合、通常はネストされたXML要素として表現されます。

このコミットで扱われる「匿名フィールド」は、このネストの挙動に影響を与え、XML構造をフラット化する可能性を秘めています。

Go Issue 3108

GoのIssue 3108は、「encoding/xml: anonymous pointer fields are not marshaled/unmarshaled」というタイトルで、匿名ポインタフィールドが encoding/xml パッケージで正しく処理されない問題について議論されています。この問題は、ポインタではない匿名構造体とは異なり、匿名ポインタ構造体(例: *MyStruct)がXML変換時に無視されるか、予期せぬ挙動を示すことを指摘しています。このコミットが作成された時点では、この問題は未解決であり、コミットはそれをドキュメントに明記することで、ユーザーに注意を促しています。

技術的詳細

このコミットは、encoding/xml パッケージの挙動、特に匿名構造体フィールドの処理に関するドキュメントとテスト例を改善しています。

  1. 匿名非ポインタ構造体のサポートの明確化:

    • src/pkg/encoding/xml/marshal.gosrc/pkg/encoding/xml/read.go のドキュメントコメントに、以下の記述が追加されました。
      • marshal.go (Marshal 関数のドキュメント内):
        //     - a non-pointer anonymous struct field is handled as if the
        //       fields of its value were part of the outer struct.
        
        これは、ポインタではない匿名構造体フィールドが、そのフィールドの値が外側の構造体の一部であるかのように扱われることを明確にしています。つまり、マーシャリング時に、埋め込まれた構造体のフィールドが、外側の構造体のXML要素の直下にフラットに展開されることを意味します。
      • read.go (Unmarshal 関数のドキュメント内):
        //   * A non-pointer anonymous struct field is handled as if the
        //      fields of its value were part of the outer struct.
        
        これは、アンマーシャリング時にも同様に、XML要素が外側の構造体の匿名フィールドの内部に直接マッピングされることを示しています。
    • このドキュメントの追加により、開発者は匿名構造体を使用する際に、XMLの構造がどのように変化するかを正確に理解できるようになります。
  2. example_test.go の更新:

    • ExampleMarshalIndent 関数と ExampleUnmarshal 関数に、Address という匿名構造体を追加し、それが Person または Result 構造体に埋め込まれる例が追加されました。
    • これにより、匿名構造体がXMLにマーシャリングされると、Address 構造体の CityState フィールドが Person または Result 要素の直下の要素として現れることが視覚的に示されます。
    • 同様に、アンマーシャリングの例でも、XML内の CityState 要素が、Go構造体の匿名 Address フィールドに正しくマッピングされることが示されています。
    • この具体的な例は、ドキュメントの記述を補完し、実際のコードでの挙動を明確に示しています。
  3. 匿名ポインタフィールドの未サポートの言及:

    • コミットメッセージと example_test.go のコメントで、匿名ポインタフィールド(例: *Address)が encoding/xml パッケージでまだサポートされていないことが明記されています。
    • この問題はGoのIssue 3108として追跡されており、このコミットは、この制限をユーザーに知らせることで、誤解やバグの発生を防ぐことを目的としています。

これらの変更は、encoding/xml パッケージのドキュメントの正確性を高め、開発者が匿名フィールドをXML処理に利用する際のガイドラインを提供します。特に、非ポインタ匿名構造体がXML構造をフラット化する挙動は、Goの構造体設計とXMLの表現をより密接に連携させる上で重要です。

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

このコミットによる主要なコード変更は以下の3つのファイルにわたります。

  1. src/pkg/encoding/xml/example_test.go:

    • ExampleMarshalIndent 関数と ExampleUnmarshal 関数に、匿名構造体 Address を含む新しい構造体定義と、そのマーシャリング/アンマーシャリングの例が追加されました。
    • Address 構造体は CityState フィールドを持ちます。
    • Person 構造体と Result 構造体に Address が匿名フィールドとして追加されています。
    • 出力されるXMLとGo構造体の値が、匿名フィールドの挙動を反映するように修正されています。
    • 匿名ポインタフィールドがまだサポートされていないことに関するコメントが追加されています。
    --- a/src/pkg/encoding/xml/example_test.go
    +++ b/src/pkg/encoding/xml/example_test.go
    @@ -11,6 +11,9 @@ import (
     )
     
     func ExampleMarshalIndent() {
    +	type Address struct {
    +		City, State string
    +	}
     	type Person struct {
     		XMLName   xml.Name `xml:"person"`
     		Id        int      `xml:"id,attr"`
    @@ -19,11 +22,13 @@ func ExampleMarshalIndent() {
     		Age       int      `xml:"age"`
     		Height    float32  `xml:"height,omitempty"`
     		Married   bool
    -		Comment   string `xml:",comment"`
    +		Address
    +		Comment string `xml:",comment"`
     	}
     
     	v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
    -	v.Comment = " Need more fields. "
    +	v.Comment = " Need more details. "
    +	v.Address = Address{"Hanga Roa", "Easter Island"}
     
     	output, err := xml.MarshalIndent(v, "  ", "    ")
     	if err != nil {
    @@ -39,7 +44,9 @@ func ExampleMarshalIndent() {
     	//       </name>
     	//       <age>42</age>
     	//       <Married>false</Married>
    -	//       <!-- Need more fields. -->
    +	//       <City>Hanga Roa</City>
    +	//       <State>Easter Island</State>
    +	//       <!-- Need more details. -->
     	//   </person>
     }
     
    @@ -52,14 +59,19 @@ func ExampleUnmarshal() {
     		Where string `xml:"where,attr"`
     		Addr  string
     	}
    +	type Address struct {
    +		City, State string
    +	}
     	type Result struct {
     		XMLName xml.Name `xml:"Person"`
     		Name    string   `xml:"FullName"`
     		Phone   string
     		Email   []Email
     		Groups  []string `xml:"Group>Value"`
    +		Address
     	}
    -	p := Result{Name: "none", Phone: "none"}
    +	v := Result{Name: "none", Phone: "none"}
    +	v.Address = Address{"Hanga Roa", "Easter Island"}
     
     	data := `
      		<Person>
    @@ -77,20 +89,22 @@ func ExampleUnmarshal() {
      			<Address>123 Main Street</Address>
      		</Person>
      	`
    -	err := xml.Unmarshal([]byte(data), &p)
    +	err := xml.Unmarshal([]byte(data), &v)
     	if err != nil {
     		fmt.Printf("error: %v", err)
     		return
     	}
    -	fmt.Printf("XMLName: %#v\n", p.XMLName)
    -	fmt.Printf("Name: %q\n", p.Name)
    -	fmt.Printf("Phone: %q\n", p.Phone)
    -	fmt.Printf("Email: %v\n", p.Email)
    -	fmt.Printf("Groups: %v\n", p.Groups)
    +	fmt.Printf("XMLName: %#v\n", v.XMLName)
    +	fmt.Printf("Name: %q\n", v.Name)
    +	fmt.Printf("Phone: %q\n", v.Phone)
    +	fmt.Printf("Email: %v\n", v.Email)
    +	fmt.Printf("Groups: %v\n", v.Groups)
    +	fmt.Printf("Address: %v\n", v.Address)
     	// Output:
     	// XMLName: xml.Name{Space:"", Local:"Person"}
     	// Name: "Grace R. Emlin"
     	// Phone: "none"
     	// Email: [{home gre@example.com} {work gre@work.com}]
     	// Groups: [Friends Squash]
    +	// Address: {Hanga Roa Easter Island}
     }
    
  2. src/pkg/encoding/xml/marshal.go:

    • Marshal 関数のドキュメントコメントに、非ポインタ匿名構造体フィールドの挙動に関する説明が追加されました。
    --- a/src/pkg/encoding/xml/marshal.go
    +++ b/src/pkg/encoding/xml/marshal.go
    @@ -57,6 +57,8 @@ const (
     //       if the field value is empty. The empty values are false, 0, any
     //       nil pointer or interface value, and any array, slice, map, or
     //       string of length zero.
    +//     - a non-pointer anonymous struct field is handled as if the
    +//       fields of its value were part of the outer struct.
     //
     // If a field uses a tag "a>b>c", then the element c will be nested inside
     // parent elements a and b.  Fields that appear next to each other that name
    
  3. src/pkg/encoding/xml/read.go:

    • Unmarshal 関数のドキュメントコメントに、非ポインタ匿名構造体フィールドの挙動に関する説明が追加されました。
    --- a/src/pkg/encoding/xml/read.go
    +++ b/src/pkg/encoding/xml/read.go
    @@ -81,6 +81,9 @@ import (
     //      of the above rules and the struct has a field with tag ",any",
     //      unmarshal maps the sub-element to that struct field.
     //
    +//   * A non-pointer anonymous struct field is handled as if the
    +//      fields of its value were part of the outer struct.
    +//
     //   * A struct field with tag "-" is never unmarshalled into.
     //
     // Unmarshal maps an XML element to a string or []byte by saving the
    

コアとなるコードの解説

このコミットのコアとなる変更は、encoding/xml パッケージが匿名構造体フィールドをどのように扱うかという、そのセマンティクスを明確化し、具体的な例で示すことにあります。

example_test.go の変更

example_test.go の変更は、匿名構造体 AddressPerson および Result 構造体に埋め込むことで、その挙動を実演しています。

  • マーシャリングの例 (ExampleMarshalIndent):

    • Person 構造体に Address が匿名フィールドとして追加されています。
    • v.Address = Address{"Hanga Roa", "Easter Island"}Address フィールドに値が設定されます。
    • 出力されるXMLを見ると、Address 構造体のフィールドである <City>Hanga Roa</City><State>Easter Island</State> が、Person 要素の直下に、あたかも Person 自身のフィールドであるかのようにフラットに展開されています。これは、Goの匿名フィールドの「フィールドの昇格」という概念がXMLマーシャリングにも適用されていることを示しています。
  • アンマーシャリングの例 (ExampleUnmarshal):

    • Result 構造体にも Address が匿名フィールドとして追加されています。
    • 入力XMLには <City>Hanga Roa</City><State>Easter Island</State>Person 要素の直下にあります。
    • アンマーシャリング後、v.Address にこれらの値が正しくマッピングされていることが fmt.Printf("Address: %v\n", v.Address) の出力で確認できます。
    • この例は、XMLのフラットな構造から、Go構造体の匿名フィールドへ正しくデータを読み込むことができることを示しています。

これらのテスト例は、非ポインタ匿名構造体がXML変換において「外側の構造体の一部であるかのように」扱われるというドキュメントの記述を具体的に裏付けています。

marshal.goread.go のドキュメント変更

marshal.goread.go のドキュメントコメントに追加された記述は、この挙動を公式に定義するものです。

  • marshal.go の変更は、xml.Marshal が非ポインタ匿名構造体のフィールドを外側の構造体のフィールドとして扱うことを明記しています。これは、XML出力がよりフラットになり、埋め込み構造体のフィールドが直接親要素の子要素として現れることを意味します。
  • read.go の変更は、xml.Unmarshal が同様にXML要素を非ポインタ匿名構造体のフィールドに直接マッピングすることを示しています。これにより、XMLの構造がGoの匿名フィールドのセマンティクスと一致するように解釈されます。

これらのドキュメントの追加は、encoding/xml パッケージの挙動に関する曖昧さを解消し、開発者が匿名フィールドを効果的に利用するための明確な指針を提供します。特に、匿名ポインタフィールドがまだサポートされていないという注意書きは、潜在的なバグや混乱を避ける上で非常に重要です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント: encoding/xml パッケージ
  • Go言語の公式ドキュメント: 構造体(Structs)と匿名フィールド(Embedded Fields)
  • Go Issue 3108の議論スレッド