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

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

このコミットは、Go言語のreflectパッケージにおけるFieldByNameおよびFieldByNameFuncメソッドのパフォーマンスを大幅に改善するものです。特に、深くネストされた構造体や匿名フィールドを持つ構造体において、フィールド検索の効率が向上しました。従来の深さ優先探索(DFS)アルゴリズムが抱えていた再探索の問題を、幅優先探索(BFS)アルゴリズムに切り替えることで解決し、同時に自明なケースに対する高速パスも追加されています。

コミット

commit 5e3224ce79f9f200940fb7e605d96c5b1499e64f
Author: Russ Cox <rsc@golang.org>
Date:   Wed Sep 5 09:35:53 2012 -0400

    reflect: faster FieldByName, FieldByNameFunc
    
    The old code was a depth first graph traversal that could, under the
    right conditions, end up re-exploring the same subgraphs multiple
    times, once for each way to arrive at that subgraph at a given depth.
    
    The new code uses a breadth first search to make sure that it only
    visits each reachable embedded struct once.
    
    Also add fast path for the trivial case.
    
    benchmark                old ns/op    new ns/op    delta
    BenchmarkFieldByName1         1321          187  -85.84%
    BenchmarkFieldByName2         6118         5186  -15.23%
    BenchmarkFieldByName3      8218553        42112  -99.49%
    
    R=gri, r
    CC=golang-dev
    https://golang.org/cl/6458090

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

https://github.com/golang/go/commit/5e3224ce79f9f2009940fb7e605d96c5b1499e64f

元コミット内容

このコミットは、Go言語のreflectパッケージ内のFieldByNameおよびFieldByNameFunc関数の速度を向上させるものです。

以前のコードは深さ優先グラフ探索(DFS)に基づいており、特定の条件下では、同じサブグラフを複数回再探索してしまう可能性がありました。これは、同じ深さでそのサブグラフに到達する複数の経路が存在する場合に発生し、非効率的でした。

新しいコードでは、幅優先探索(BFS)を使用することで、到達可能な埋め込み構造体をそれぞれ一度だけ訪問するように変更されました。これにより、冗長な探索が排除されます。

さらに、自明なケース(例えば、トップレベルのフィールドを直接検索する場合など)に対する高速パスが追加され、全体的なパフォーマンスが向上しています。

ベンチマーク結果は以下の通り、大幅な改善を示しています。

  • BenchmarkFieldByName1: 1321 ns/op から 187 ns/op へ、85.84%の改善。
  • BenchmarkFieldByName2: 6118 ns/op から 5186 ns/op へ、15.23%の改善。
  • BenchmarkFieldByName3: 8218553 ns/op から 42112 ns/op へ、99.49%という劇的な改善。

変更の背景

Go言語のreflectパッケージは、実行時に型情報を検査・操作するための強力な機能を提供します。特に、構造体のフィールドを名前で検索するFieldByNameや、カスタムのマッチング関数で検索するFieldByNameFuncは、フレームワークやライブラリで頻繁に利用されます。

しかし、従来のFieldByNameFuncの実装は、深さ優先探索(DFS)アルゴリズムを採用していました。このDFSベースの実装は、特に以下のようなシナリオで性能上の問題を抱えていました。

  1. 冗長な再探索: 構造体が複数の匿名フィールドを介して同じ型の埋め込み構造体を参照している場合、DFSは同じ埋め込み構造体を異なるパスで複数回訪問し、そのたびに内部のフィールドを再探索してしまう可能性がありました。これにより、探索空間が指数関数的に増大し、パフォーマンスが著しく低下することがありました。
  2. フィールドの相殺ルールの複雑性: Go言語には、構造体内に同じ名前のフィールドが複数存在する場合の「フィールドの相殺(field annihilation)」というルールがあります。これは、同じ深さレベルで同じ名前のフィールドが複数見つかった場合、それらのフィールドは互いに「相殺」し、その名前ではアクセスできなくなるというものです。DFSは、このルールを正確かつ効率的に処理するのが難しい場合があります。

BenchmarkFieldByName3のベンチマーク結果が示すように、特定の複雑な構造体(特に深いネストと多数の埋め込みフィールドを持つもの)では、フィールド検索に数ミリ秒かかることがあり、これはアプリケーションの応答性に大きな影響を与える可能性がありました。この性能ボトルネックを解消し、reflectパッケージの堅牢性と効率性を高めることが、このコミットの主要な動機となりました。

前提知識の解説

Go言語のreflectパッケージ

Go言語のreflectパッケージは、プログラムの実行時に変数や関数の型情報を動的に取得・操作するための機能を提供します。これにより、コンパイル時には型が不明なデータ(例えば、JSONデコードされたデータやデータベースから取得したデータ)を扱う汎用的なコードを書くことが可能になります。

  • reflect.Type: Goの型を表すインターフェースです。reflect.TypeOf(v)で任意の変数のTypeを取得できます。
  • reflect.Value: Goの値を表す構造体です。reflect.ValueOf(v)で任意の変数のValueを取得できます。
  • StructField: 構造体の個々のフィールドに関する情報(名前、型、タグ、インデックスなど)を保持する構造体です。

Goの構造体と匿名フィールド(埋め込みフィールド)

Goの構造体は、異なる型のフィールドをまとめるための複合型です。Goには「匿名フィールド」または「埋め込みフィールド」と呼ばれる特別な機能があります。これは、フィールド名を指定せずに型だけを構造体に含めることで、その型のフィールドやメソッドを外側の構造体が「継承」したかのように振る舞わせるものです。

例:

type Inner struct {
    ID int
    Name string
}

type Outer struct {
    Inner // 匿名フィールド
    Description string
}

func main() {
    o := Outer{Inner: Inner{ID: 1, Name: "Test"}, Description: "Example"}
    fmt.Println(o.ID)   // OuterからInnerのIDフィールドに直接アクセスできる
    fmt.Println(o.Name) // OuterからInnerのNameフィールドに直接アクセスできる
}

FieldByNameFieldByNameFuncの役割

  • Type.FieldByName(name string) (StructField, bool): 構造体の型(reflect.Type)に対して呼び出され、指定された名前のフィールドを検索します。フィールドが見つかればそのStructFieldtrueを返し、見つからなければゼロ値のStructFieldfalseを返します。
  • Type.FieldByNameFunc(match func(string) bool) (StructField, bool): FieldByNameのより汎用的なバージョンです。フィールド名を引数にとるmatch関数を渡すことで、カスタムの条件でフィールドを検索できます。match関数がtrueを返した最初のフィールドが返されます。FieldByNameは内部的にFieldByNameFuncを呼び出しています。

Goのフィールド検索ルール(特に「フィールドの相殺(annihilation)」)

Go言語の仕様では、構造体のフィールド検索には特定のルールがあります。特に重要なのが「フィールドの相殺(field annihilation)」です。

  • 優先順位: フィールド検索は、まず構造体自身のトップレベルのフィールドを検索します。見つからなければ、埋め込みフィールドのフィールドを再帰的に検索します。
  • 深さ優先: 埋め込みフィールドを介して同じ名前のフィールドが複数見つかった場合、より浅い深さ(つまり、元の構造体に近い)にあるフィールドが優先されます。
  • 相殺(Annihilation): 同じ深さレベルで、同じ名前のフィールドが複数見つかった場合、それらのフィールドは互いに「相殺」し、その名前ではアクセスできなくなります。この場合、FieldByNameはフィールドを見つけられなかったと判断します。このルールは、Go言語の型システムが曖昧さを避けるために導入されています。

例:

type A struct { X int }
type B struct { X int }
type C struct { A; B } // AとBが匿名で埋め込まれている

func main() {
    c := C{}
    // c.X はコンパイルエラーになる。
    // A.X と B.X が同じ深さレベル(Cの直下)にあり、互いに相殺するため。
    // アクセスするには c.A.X または c.B.X と明示する必要がある。
}

深さ優先探索(DFS)と幅優先探索(BFS)の概念と違い

グラフ探索アルゴリズムには、主に深さ優先探索(DFS)と幅優先探索(BFS)があります。

  • 深さ優先探索(DFS - Depth-First Search):

    • 現在のノードから可能な限り深く探索を進めます。
    • 行き止まりに到達するか、既に訪問したノードに到達すると、一つ前のノードに戻り、別の未訪問のパスを探索します。
    • 再帰またはスタックを使って実装されることが多いです。
    • 特徴: 探索パスが長くなる傾向があります。特定のパスの存在確認や、すべてのノードを訪問するのに適しています。
  • 幅優先探索(BFS - Breadth-First Search):

    • 現在のノードから、隣接するすべてのノードを探索します。
    • 現在の深さレベルのすべてのノードを訪問し終えてから、次の深さレベルのノードへと進みます。
    • キューを使って実装されます。
    • 特徴: 最短パスを見つけるのに適しています。各ノードを一度だけ訪問するため、冗長な探索が少ないです。

Goのフィールド検索において、DFSは深くネストされた構造体を優先的に探索するため、相殺ルールを正確に適用したり、既に探索済みのサブグラフを効率的にスキップしたりするのが難しいという問題がありました。一方、BFSは深さレベルごとに探索を進めるため、相殺ルール(同じ深さレベルでの重複)の適用が自然であり、各埋め込み構造体を一度だけ訪問することで効率的な探索が可能になります。

技術的詳細

このコミットの核心は、reflectパッケージのFieldByNameFuncメソッドにおけるフィールド検索アルゴリズムを、従来の深さ優先探索(DFS)から幅優先探索(BFS)へと変更した点にあります。この変更により、特に複雑な構造体におけるフィールド検索のパフォーマンスと正確性が大幅に向上しました。

旧実装のDFSの問題点

従来のfieldByNameFunc(内部関数)は、再帰的なDFSアプローチを採用していました。このアプローチの主な問題点は以下の通りです。

  1. 冗長な探索: 複数の匿名フィールドが同じ埋め込み型を参照している場合、DFSはそれぞれのパスからその埋め込み型を再探索してしまう可能性がありました。これにより、探索空間が重複し、計算コストが不必要に増大しました。
  2. 相殺ルールの複雑な処理: Goのフィールド相殺ルールは「同じ深さレベル」での重複を検出する必要があります。DFSは深さ方向に深く進むため、同じ深さレベルにあるフィールドを効率的に比較・管理するのが困難でした。旧コードではn(同じ深さでマッチしたフィールド数)とfd(フィールドの深さ)を追跡していましたが、これが複雑さを増していました。
  3. サイクル検出の必要性: 埋め込みポインタフィールドが存在する場合、構造体のグラフにサイクルが発生する可能性があります。DFSは無限ループに陥るのを避けるために、markマップ(map[*structType]bool)を使用して既に訪問した構造体を追跡する必要がありました。

新実装のBFSの利点

新しいFieldByNameFuncは、これらの問題を解決するためにBFSを採用しています。BFSは深さレベルごとに探索を進めるため、以下の利点があります。

  1. 効率的な探索: 各埋め込み構造体を一度だけ訪問することを保証します。これにより、冗長な再探索が排除され、特に深いネストや多数の埋め込みフィールドを持つ構造体でのパフォーマンスが劇的に向上します。
  2. 相殺ルールの自然な適用: BFSは深さレベルごとに処理を行うため、同じ深さレベルで複数のフィールドがマッチした場合の相殺ルールを自然かつ効率的に適用できます。countマップ(map[*structType]int)とokフラグ(現在の深さレベルでマッチが見つかったか)を組み合わせることで、このルールを正確に実装しています。
  3. サイクル検出と重複回避: visitedマップ(map[*structType]bool)は、既に上位の深さレベルで探索された構造体を追跡し、それらを下位の深さレベルで再探索しないようにします。これにより、ポインタによるサイクルを回避しつつ、効率的な探索を維持します。

BFSアルゴリズムの実装詳細

新しいFieldByNameFuncは、以下の主要な要素で構成されています。

  • fieldScan構造体:

    type fieldScan struct {
        typ   *structType // 現在探索中の構造体の型
        index []int       // 現在のフィールドへのパスを示すインデックススライス
    }
    

    これはBFSのキュー(currentnext)に格納される要素で、探索中の構造体とその構造体へのパス(index)を保持します。

  • currentnextキュー: currentは現在の深さレベルで処理すべきfieldScan要素のリスト、nextは次の深さレベルで処理すべき要素のリストです。ループの各イテレーションで、currentnextが入れ替わり、currentの要素が処理され、その結果がnextに追加されます。

  • nextCountマップ: map[*structType]int型で、次の深さレベルで特定の埋め込み構造体型が何回キューに追加されようとしているかを記録します。Goの相殺ルールでは、同じ深さレベルで同じ名前のフィールドが複数回見つかると相殺されるため、このマップは、埋め込み型が複数回参照されている場合に、その埋め込み型自体が相殺されるべきかどうかを判断するために使用されます。もしnextCount[styp] > 1であれば、その埋め込み型は次のレベルで相殺されるため、それ以上探索する必要がありません。

  • visitedマップ: map[*structType]bool型で、既に探索済みの構造体型を記録します。これは、埋め込みポインタフィールドによる無限ループ(サイクル)を防ぐとともに、既に上位の深さレベルで探索された構造体を下位の深さレベルで再探索するのを避けるために使用されます。

  • 探索ロジック:

    1. nextキューに初期の構造体(t)を追加して探索を開始します。
    2. ループはnextキューが空になるまで続きます。
    3. 各イテレーションで、currentnextを入れ替え、nextCountをリセットします。
    4. currentキューの各fieldScan要素について、以下の処理を行います。
      • もしその構造体型がvisitedマップに既に存在すればスキップします(上位レベルで既に探索済みのため)。
      • 現在の構造体の各フィールドを反復処理します。
      • フィールドが匿名フィールドの場合、その型から名前を推測します。
      • match関数を使ってフィールド名がマッチするかどうかをチェックします。
      • マッチした場合:
        • もしcount[t] > 1(現在の構造体型がこの深さレベルで複数回参照されている)または既にok(この深さレベルで別のマッチが見つかっている)であれば、相殺ルールによりフィールドは見つからないため、即座にStructField{}, falseを返して終了します。
        • そうでなければ、このフィールドが結果として返されるフィールドとなり、oktrueに設定します。
      • マッチしない場合(かつ匿名フィールドの場合):
        • その匿名フィールドが構造体型であれば、次の深さレベルで探索するためにnextキューに追加します。この際、nextCountマップを更新して、同じ埋め込み型が複数回追加されないように、また相殺ルールを適用できるようにします。
  • 自明なケースの高速パス: FieldByName関数には、匿名フィールドを持たない構造体や、トップレベルで名前がマッチするフィールドがある場合の高速パスが追加されました。これにより、複雑なBFSロジックを実行する前に、簡単なケースを迅速に処理できるようになり、全体的なパフォーマンスがさらに向上します。

このBFSベースの新しい実装により、reflectパッケージのフィールド検索は、より堅牢で予測可能になり、特に複雑な型構造を持つGoアプリケーションの性能向上に貢献しました。

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

このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。

  1. src/pkg/reflect/all_test.go:

    • 新しいテストケースとベンチマークが追加されました。
    • 特に、深くネストされた埋め込み構造体や、フィールドの相殺ルールをテストするための複雑な構造体定義(S5, S6, S7, S8, S9, S10, S11, S12, S13など)が追加されています。
    • BenchmarkFieldByName1, BenchmarkFieldByName2, BenchmarkFieldByName3という新しいベンチマーク関数が追加され、変更前後のパフォーマンス比較に使用されました。特にBenchmarkFieldByName3は、非常に深いネストを持つ構造体(R0からR24まで)を定義し、最悪ケースのパフォーマンスを測定しています。
  2. src/pkg/reflect/type.go:

    • structType型のメソッドであるfieldByNameFunc(旧実装の内部関数)が削除され、そのロジックがFieldByNameFuncメソッドに統合されました。
    • FieldByNameFuncメソッドの実装が、深さ優先探索(DFS)から幅優先探索(BFS)へと完全に書き換えられました。
      • fieldScanという新しいヘルパー構造体が定義されました。
      • current, nextというfieldScanのスライスが、BFSのキューとして導入されました。
      • nextCountvisitedというマップが、それぞれフィールドの相殺ルールとサイクル検出/重複回避のために導入されました。
    • FieldByNameメソッドも変更され、自明なケース(トップレベルのフィールド検索や匿名フィールドがない場合)に対する高速パスが追加されました。これにより、複雑なFieldByNameFuncの呼び出しを避けることができます。

コアとなるコードの解説

ここでは、src/pkg/reflect/type.goにおけるFieldByNameFuncの新しい実装を中心に解説します。

FieldByNameFunc (新しい実装)

func (t *structType) FieldByNameFunc(match func(string) bool) (result StructField, ok bool) {
	// Go言語のフィールド検索ルールに従う:
	// 特定の深さレベルでマッチするユニークなインスタンスが存在する必要がある。
	// 同じ深さレベルで複数のマッチがある場合、それらは互いに相殺し、
	// より低いレベルでのマッチを阻害する。
	// アルゴリズムは幅優先探索(BFS)で、一度に一つの深さレベルを処理する。

	// current と next スライスは作業キュー:
	// current は現在の深さレベルで訪問すべきフィールドをリストし、
	// next は次の低い深さレベルのフィールドをリストする。
	current := []fieldScan{}
	next := []fieldScan{{typ: t}} // 最初の構造体型で初期化

	// nextCount は、埋め込み型が 'next' スライスにキューイングされるために
	// 遭遇し、考慮された回数を記録する。
	// 最初のものだけをキューに入れるが、遭遇するたびにカウントを増やす。
	// もし構造体型 T が特定の深さレベルで複数回到達可能であれば、
	// それは自身を相殺し、次の深さレベルを処理する際に全く考慮する必要がない。
	var nextCount map[*structType]int

	// visited は、既に考慮された構造体を記録する。
	// 埋め込みポインタフィールドは、到達可能な埋め込み型のグラフにサイクルを作成する可能性がある。
	// visited はそれらのサイクルを追跡するのを避ける。
	// また、重複した作業も避ける: もしレベル2の埋め込み型 T でフィールドが見つからなかった場合、
	// レベル4の埋め込み型 T でも見つからない。
	visited := map[*structType]bool{}

	for len(next) > 0 {
		// current と next を入れ替える。current は現在の深さレベルの要素になる。
		current, next = next, current[:0]
		// 前のイテレーションの nextCount を現在の count として使用。
		count := nextCount
		// 次のイテレーションのための nextCount をリセット。
		nextCount = nil

		// この深さレベルのすべてのフィールドを処理する。これらは 'current' にリストされている。
		// このループは、次のイテレーションで処理するために 'next' に埋め込みフィールドをキューイングする。
		// 'current' フィールドのカウントの多重度は 'count' に記録され、
		// 'next' フィールドのカウントの多重度は 'nextCount' に記録される。
		for _, scan := range current {
			t := scan.typ
			if visited[t] {
				// この型は以前、より高いレベルで調べられている。
				// その高いレベルが、現在いる低いレベルをシャドウするため、
				// この型は役に立たない。無視する。
				continue
			}
			visited[t] = true // この型を訪問済みとしてマーク

			for i := range t.fields {
				f := &t.fields[i]
				var fname string
				var ntyp *commonType // 匿名フィールドの場合の型情報

				if f.name != nil {
					// 名前付きフィールド
					fname = *f.name
				} else {
					// 匿名フィールド (T または *T)
					// 名前は型から取得する。
					ntyp = toCommonType(f.typ)
					if ntyp.Kind() == Ptr {
						ntyp = ntyp.Elem().common()
					}
					fname = ntyp.Name()
				}

				// マッチするか?
				if match(fname) {
					// 潜在的なマッチ
					if count[t] > 1 || ok {
						// このレベルで名前が複数回出現した: 相殺する。
						// または、このレベルで既にマッチが見つかっている。
						return StructField{}, false // 相殺により見つからない
					}
					// マッチが見つかった。結果を構築。
					result = t.Field(i)
					result.Index = nil
					result.Index = append(result.Index, scan.index...) // 親のインデックスパスを追加
					result.Index = append(result.Index, i)             // 現在のフィールドのインデックスを追加
					ok = true // マッチが見つかったことを示す
					continue  // このフィールドの処理を終了し、次のフィールドへ
				}

				// 埋め込み構造体フィールドを次のレベルで処理するためにキューに入れる。
				// ただし、このレベルでまだマッチが見つかっていない場合、
				// かつ、埋め込み型が既にキューに入れられていない場合に限る。
				if ok || ntyp == nil || ntyp.Kind() != Struct {
					continue // マッチが見つかったか、構造体ではない場合はスキップ
				}
				styp := (*structType)(unsafe.Pointer(ntyp))
				if nextCount[styp] > 0 {
					nextCount[styp]++ // 既にキューにある場合はカウントを増やす
					continue
				}
				if nextCount == nil {
					nextCount = map[*structType]int{}
				}
				nextCount[styp] = 1 // 新しくキューに入れる
				var index []int
				index = append(index, scan.index...)
				index = append(index, i)
				next = append(next, fieldScan{styp, index}) // 次のレベルのキューに追加
			}
		}
		if ok {
			break // この深さレベルでマッチが見つかったので、探索を終了
		}
	}
	return // フィールドが見つからなかった場合、ゼロ値と false を返す
}

FieldByName (新しい実装)

func (t *structType) FieldByName(name string) (f StructField, present bool) {
	// トップレベルの名前、または匿名フィールドのない構造体に対する高速チェック。
	hasAnon := false
	if name != "" {
		for i := range t.fields {
			tf := &t.fields[i]
			if tf.name == nil {
				hasAnon = true // 匿名フィールドがある
				continue
			}
			if *tf.name == name {
				// トップレベルでマッチが見つかった場合、即座に返す。
				return t.Field(i), true
			}
		}
	}
	if !hasAnon {
		// 匿名フィールドがない場合、トップレベルでマッチが見つからなければ、
		// それ以上探索する必要はない。
		return
	}
	// 匿名フィールドがあり、トップレベルでマッチが見つからなかった場合、
	// FieldByNameFunc を呼び出して詳細な探索を行う。
	return t.FieldByNameFunc(func(s string) bool { return s == name })
}

解説のポイント

  1. BFSのループ構造: for len(next) > 0 ループがBFSの核です。currentnextのスライスを入れ替えることで、深さレベルごとに探索を進めます。
  2. fieldScan: 探索中の構造体とそのフィールドへのパス(index)を保持し、結果のStructField.Indexを正確に構築するために使用されます。
  3. visitedマップ: 既に探索済みの構造体型を記録することで、無限ループ(ポインタによるサイクル)を防ぎ、また、より上位の深さレベルで既に探索された構造体を下位の深さレベルで再探索する冗長性を排除します。
  4. nextCountマップと相殺ルール:
    • Goのフィールド相殺ルールは「同じ深さレベル」で同じ名前のフィールドが複数見つかると、その名前ではアクセスできなくなるというものです。
    • nextCountは、次の深さレベルで特定の埋め込み構造体型が何回キューに追加されようとしているかを追跡します。
    • count[t] > 1のチェックは、現在の深さレベルで、現在の構造体型tが複数回参照されている(つまり、相殺されるべき)場合に、そのフィールドを無効と判断するために使用されます。
    • okフラグは、現在の深さレベルで既にマッチが見つかっているかどうかを示します。もしoktrueであれば、それ以上同じ深さレベルで別のマッチを探す必要はなく、相殺ルールにより最初のマッチが有効となります。
  5. 匿名フィールドの処理: f.name == nilで匿名フィールドを識別し、その型から名前を推測します。匿名フィールドが構造体型であれば、nextキューに追加して次の深さレベルで探索を続けます。
  6. 高速パス: FieldByNameに導入された高速パスは、匿名フィールドがない場合や、トップレベルでフィールドが見つかる場合に、より複雑なFieldByNameFuncの呼び出しをスキップすることで、一般的なケースでのオーバーヘッドを削減します。

この新しい実装は、Goのreflectパッケージのフィールド検索を、より効率的で、Go言語のフィールド検索ルール(特に相殺)に厳密に準拠したものにしました。

関連リンク

  • Go言語仕様 - Struct types: https://go.dev/ref/spec#Struct_types
    • 特に「Anonymous fields」と「Field and method expressions」のセクションが、フィールド検索と相殺ルールに関連します。
  • Go言語仕様 - Selectors: https://go.dev/ref/spec#Selectors
    • フィールド選択のルールについて記述されています。
  • reflectパッケージのドキュメント: https://pkg.go.dev/reflect
    • Type.FieldByNameおよびType.FieldByNameFuncの公式ドキュメント。

参考にした情報源リンク

  • この解説は、提供されたコミットメッセージ、変更されたソースコード(src/pkg/reflect/all_test.gosrc/pkg/reflect/type.go)、およびGo言語の公式ドキュメントと仕様に基づいて作成されました。
  • 特定のWeb検索は行っていません。