[インデックス 14398] ファイルの概要
コミット
commit aa3880178850ab0525802a48fc4eeadcdbb2c26c
Author: Robert Griesemer <gri@golang.org>
Date: Tue Nov 13 10:45:30 2012 -0800
reflect: fix FieldByNameFunc
The existing algorithm did not properly propagate the type
count from one level to the next, and as a consequence it
missed collisions.
Properly propagate multiplicity (count) information to the
next level.
benchmark old ns/op new ns/op delta
BenchmarkFieldByName1 182 180 -1.10%
BenchmarkFieldByName2 6273 6183 -1.43%
BenchmarkFieldByName3 49267 46784 -5.04%
Fixes #4355.
R=rsc
CC=golang-dev
https://golang.org/cl/6821094
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aa3880178850ab0525802a48fc4eeadcdbb2c26c
元コミット内容
このコミットは、Go言語のreflect
パッケージにおけるFieldByNameFunc
関数のバグ修正を目的としています。既存のアルゴリズムが、構造体の埋め込みフィールドを探索する際に、型の出現回数(multiplicity/count)を適切に伝播していなかったため、名前の衝突(collisions)を見逃していました。この修正により、型の出現回数情報を次のレベルに正しく伝播させることで、衝突を正確に検出できるようになります。
ベンチマーク結果では、FieldByName
関連の操作においてパフォーマンスがわずかに向上していることが示されています。
BenchmarkFieldByName1
: 182 ns/op -> 180 ns/op (-1.10%)BenchmarkFieldByName2
: 6273 ns/op -> 6183 ns/op (-1.43%)BenchmarkFieldByName3
: 49267 ns/op -> 46784 ns/op (-5.04%)
この修正は、Issue #4355を解決します。
変更の背景
Go言語のreflect
パッケージは、実行時に型情報を検査・操作するための機能を提供します。特に、構造体のフィールドに名前でアクセスする機能は、様々なメタプログラミングやシリアライゼーションのシナリオで重要です。
FieldByNameFunc
は、特定の条件(関数で定義されるマッチングロジック)に合致する名前のフィールドを構造体から検索する関数です。Goの構造体は、他の構造体を埋め込む(embedding)ことができます。これにより、埋め込まれた構造体のフィールドが、埋め込み元の構造体のフィールドであるかのようにアクセスできるようになります。
この埋め込みのメカニズムは非常に便利ですが、複数のパスから同じ名前のフィールドに到達できる場合、名前の衝突(ambiguity)が発生する可能性があります。例えば、構造体Aが構造体Bと構造体Cを埋め込み、BとCの両方が同じ名前のフィールドXを持っている場合、A.Xにアクセスしようとすると曖昧になります。Goのreflect
パッケージは、このような曖昧なケースではフィールドを返すべきではありません。
しかし、このコミット以前のFieldByNameFunc
の実装では、埋め込み構造体を再帰的に探索する際に、既に探索済みの型やその出現回数を正確に追跡できていませんでした。具体的には、ある型が複数の埋め込みパスを通じて到達可能であるにもかかわらず、その「複数回出現している」という情報が次の探索レベルに適切に伝播されなかったため、衝突を誤って見逃し、曖昧なフィールドを返してしまうバグが存在しました。
このバグは、Issue #4355で報告されており、reflect
パッケージの正確性と堅牢性を確保するために修正が必要でした。
前提知識の解説
Go言語のreflect
パッケージ
reflect
パッケージは、Goプログラムが自身の構造を検査する(リフレクション)ための機能を提供します。これにより、プログラムは実行時に変数や関数の型情報を取得したり、値を動的に操作したりすることができます。主な用途は以下の通りです。
- 型情報の取得: 変数の型、メソッド、フィールドなどの情報を取得します。
- 値の操作: リフレクションを使って、変数の値を読み書きしたり、メソッドを呼び出したりします。
- 構造体のタグの利用: 構造体のフィールドに付与されたタグ(例:
json:"name"
)を読み取り、シリアライゼーションやバリデーションに利用します。
reflect
パッケージは強力ですが、型安全性を損なう可能性があり、パフォーマンスオーバーヘッドも伴うため、必要不可欠な場合にのみ使用することが推奨されます。
構造体の埋め込み(Embedding)
Go言語の構造体は、他の構造体を匿名フィールドとして含めることができます。これを「埋め込み」と呼びます。埋め込まれた構造体のフィールドやメソッドは、あたかも埋め込み元の構造体のフィールドやメソッドであるかのように、直接アクセスできます。
例:
type Point struct {
X, Y int
}
type Circle struct {
Point // Point構造体を埋め込み
Radius int
}
func main() {
c := Circle{Point: Point{X: 10, Y: 20}, Radius: 5}
fmt.Println(c.X) // CircleからPointのXフィールドに直接アクセス
}
フィールド名の衝突(Field Name Collisions)
構造体の埋め込みにおいて、異なるパスから同じ名前のフィールドに到達できる場合、フィールド名の衝突が発生します。Goの言語仕様では、このような曖昧なアクセスは許可されません。reflect
パッケージも同様に、曖昧なフィールドを検索した場合には、そのフィールドを返すべきではありません。
例:
type S1 struct { X int }
type S2 struct { S1 }
type S3 struct { S1 }
type S4 struct { S2; S3 } // S2とS3の両方がS1を埋め込んでいるため、S4.Xは曖昧
この場合、S4{}.X
に直接アクセスしようとするとコンパイルエラーになります。reflect
パッケージのFieldByName
やFieldByNameFunc
も、このようなケースではフィールドが見つからない(nil
を返す)ように振る舞うべきです。
FieldByNameFunc
の内部動作(簡略化)
FieldByNameFunc
は、指定された関数(match
)を使ってフィールド名を探索します。内部的には、構造体のフィールドと、埋め込まれた構造体のフィールドを再帰的に探索します。この探索プロセスでは、各フィールドがどのパスを通じて到達可能か、そしてそのパスが複数存在するかどうかを追跡する必要があります。
この追跡のために、内部でcount
のようなマップが使用されます。これは、特定の型が現在の探索レベルで何回出現したかを記録するためのものです。もしある型が2回以上出現した場合、その型を通じてアクセスできるフィールドは曖昧であると判断され、結果から除外されるべきです。
技術的詳細
このコミットの技術的詳細は、reflect
パッケージのFieldByNameFunc
メソッドにおける、埋め込み構造体の探索ロジック、特に「型の出現回数(multiplicity/count)」の伝播方法にあります。
FieldByNameFunc
は、構造体のフィールドを深さ優先探索(またはそれに近い方法)で走査し、指定されたマッチング関数に合致するフィールドを探します。この際、埋め込み構造体に出くわすと、その埋め込み構造体のフィールドも探索対象に加えます。
バグの核心は、nextCount
というマップの更新ロジックにありました。nextCount
は、次の探索レベルで処理される埋め込み構造体の型が、これまでに何回検出されたかを追跡するためのものです。
修正前のコードでは、埋め込み構造体styp
がnextCount
に既に存在する場合、単にnextCount[styp]++
としていました。これは、その型が複数回出現したことを示すには十分ですが、その「複数回出現している」という状態が、さらに深いレベルの探索に適切に伝播されない可能性がありました。
例えば、A
がB
とC
を埋め込み、B
とC
が両方ともD
を埋め込んでいる場合を考えます。
A -> B -> D
A -> C -> D
この場合、D
はA
から2つの異なるパスで到達可能です。FieldByNameFunc
がB
とC
を処理する際に、D
がnextCount
に2回追加されるべきです。しかし、もしB
を処理した時点でD
がnextCount
に追加され、次にC
を処理した際にD
が既に存在するためnextCount[D]++
となるだけだと、そのD
が「曖昧である」という情報が、D
の内部フィールドを探索する際に失われる可能性がありました。
修正後のコードでは、この伝播ロジックが強化されています。
-
if nextCount[styp] > 0 { nextCount[styp] = 2; continue }
- これは、
styp
が既にnextCount
に存在する場合(つまり、既に一度検出されている場合)、そのカウントを2
に設定します。2
という値は、その型が「複数回出現している」ことを明確に示します。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要です。これにより、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。
- これは、
-
if count[t] > 1 { nextCount[styp] = 2 }
- これはさらに重要な変更です。現在の探索レベルの型
t
自体が既に曖昧である(つまり、count[t] > 1
)場合、そのt
が埋め込んでいるstyp
もまた、曖昧なパスを通じて到達可能であると見なされます。したがって、styp
のnextCount
も2
に設定されます。これにより、曖昧さの情報が探索の深さに応じて正しく伝播されるようになります。
- これはさらに重要な変更です。現在の探索レベルの型
この変更により、FieldByNameFunc
は、複数の埋め込みパスを通じて到達可能なフィールド(つまり、曖昧なフィールド)を正しく識別し、それらを結果として返さないようになります。これは、Goのリフレクションが言語の曖昧性解決ルールに厳密に従うために不可欠です。
コアとなるコードの変更箇所
変更は主に以下の2つのファイルで行われています。
-
src/pkg/reflect/all_test.go
- 新しいテストケースが追加されています。
S14
,S15
,S16
という構造体が定義されています。S14
はS15
とS16
を埋め込み、S15
とS16
は両方ともS11
を埋め込んでいます。S11
はX
というフィールドを持っています。fieldTests
配列に{S14{}, "X", nil, 0}
というエントリが追加されています。これは、S14
型に対してX
という名前のフィールドを検索した場合、結果がnil
(フィールドが見つからない)になることを期待しています。これは、S14.X
がS15.S11.X
とS16.S11.X
の両方から到達可能であるため、曖昧であるというシナリオをテストしています。
-
src/pkg/reflect/type.go
(*structType).FieldByNameFunc
メソッド内のロジックが変更されています。- 具体的には、埋め込み構造体を処理する部分で、
nextCount
マップの更新方法が修正されています。
--- a/src/pkg/reflect/type.go
+++ b/src/pkg/reflect/type.go
@@ -913,19 +913,22 @@ func (t *structType) FieldByNameFunc(match func(string) bool) (result StructFiel
// Queue embedded struct fields for processing with next level,
// but only if we haven't seen a match yet at this level and only
- // if the embedded types haven't alredy been queued.
+ // if the embedded types haven't already been queued.
if ok || ntyp == nil || ntyp.Kind() != Struct {
continue
}
styp := (*structType)(unsafe.Pointer(ntyp))
if nextCount[styp] > 0 {
- nextCount[styp]++
+ nextCount[styp] = 2 // exact multiple doesn't matter
continue
}
if nextCount == nil {
nextCount = map[*structType]int{}
}
nextCount[styp] = 1
+ if count[t] > 1 {
+ nextCount[styp] = 2 // exact multiple doesn't matter
+ }
var index []int
index = append(index, scan.index...)
index = append(index, i)
コアとなるコードの解説
src/pkg/reflect/type.go
のFieldByNameFunc
メソッド内の変更点に焦点を当てて解説します。
このコードブロックは、構造体のフィールドを探索するループ内で、埋め込み構造体(ntyp
がStruct
型の場合)を処理する部分です。
-
if nextCount[styp] > 0 { nextCount[styp] = 2; continue }
styp
は現在処理している埋め込み構造体の型です。nextCount
は、次の探索レベルで処理される埋め込み構造体の型と、それがこれまでに検出された回数を記録するマップです。- 変更前:
nextCount[styp]++
styp
が既に検出されている場合、単にそのカウントをインクリメントしていました。
- 変更後:
nextCount[styp] = 2
styp
が既に検出されている場合(つまり、nextCount[styp]
が0
より大きい場合)、そのカウントを2
に設定します。これは、その型が「複数回出現している」ことを明確にマークするためのものです。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要であるため、2
という固定値が使われます。これにより、この型を通じてアクセスできるフィールドは曖昧であると判断され、最終的に返されなくなります。
-
if count[t] > 1 { nextCount[styp] = 2 }
- この行は新たに追加されたロジックです。
t
は現在の探索レベルで処理している構造体の型です。count[t]
は、現在の構造体t
が、上位のレベルから複数のパスを通じて到達可能であるかどうかを示します(count[t] > 1
であれば曖昧)。- 追加されたロジック: もし現在の構造体
t
自体が既に曖昧なパスを通じて到達可能である場合、そのt
が埋め込んでいるstyp
もまた、曖昧なパスを通じて到達可能であると見なされます。したがって、styp
のnextCount
も2
に設定され、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。
これらの変更により、FieldByNameFunc
は、埋め込み構造体の探索において、型の出現回数(multiplicity)情報をより正確に伝播できるようになりました。これにより、複数の埋め込みパスを通じて到達可能なフィールド(名前の衝突があるフィールド)を正しく識別し、それらを結果として返さないという、Go言語の仕様に準拠した振る舞いを実現しています。
関連リンク
- Go言語の
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語の構造体の埋め込みに関する公式ドキュメントやチュートリアル(Goのバージョンによってリンクが異なる可能性がありますが、
Go embedding
などで検索すると見つかります)
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/aa3880178850ab0525802a48fc4eeadcdbb2c26c
- Go Issue #4355:
reflect: FieldByNameFunc misses collisions
(このIssueはGitHubのコミットページからリンクされていますが、直接のリンクは提供されていません。GoのIssueトラッカーで検索する必要があります。) - Go CL 6821094: https://golang.org/cl/6821094 (GoのコードレビューシステムGerritのリンク)
[インデックス 14398] ファイルの概要
コミット
commit aa3880178850ab0525802a48fc4eeadcdbb2c26c
Author: Robert Griesemer <gri@golang.org>
Date: Tue Nov 13 10:45:30 2012 -0800
reflect: fix FieldByNameFunc
The existing algorithm did not properly propagate the type
count from one level to the next, and as a consequence it
missed collisions.
Properly propagate multiplicity (count) information to the
next level.
benchmark old ns/op new ns/op delta
BenchmarkFieldByName1 182 180 -1.10%
BenchmarkFieldByName2 6273 6183 -1.43%
BenchmarkFieldByName3 49267 46784 -5.04%
Fixes #4355.
R=rsc
CC=golang-dev
https://golang.org/cl/6821094
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aa3880178850ab0525802a48fc4eeadcdbb2c26c
元コミット内容
このコミットは、Go言語の標準ライブラリであるreflect
パッケージ内のFieldByNameFunc
関数のバグを修正するものです。既存の実装では、構造体のフィールドを名前で検索する際に、特に埋め込み構造体(embedded structs)を扱う場合に、型の出現回数(multiplicity/count)が次の探索レベルに適切に伝播されないという問題がありました。この伝播の欠如により、本来検出されるべきフィールド名の衝突(collisions)が見逃されていました。
この修正の目的は、型の出現回数情報を探索の次のレベルに正しく伝播させることで、フィールド名の衝突を正確に識別し、Go言語の仕様に沿った振る舞いを保証することです。
ベンチマーク結果は、この修正がFieldByName
関連の操作のパフォーマンスにわずかながら改善をもたらしたことを示しています。
BenchmarkFieldByName1
: 182 ns/op から 180 ns/op へ (-1.10%)BenchmarkFieldByName2
: 6273 ns/op から 6183 ns/op へ (-1.43%)BenchmarkFieldByName3
: 49267 ns/op から 46784 ns/op へ (-5.04%)
この修正は、GoのIssue #4355を解決します。
変更の背景
Go言語のreflect
パッケージは、実行時にプログラムの型情報を検査・操作するための強力な機能を提供します。これにより、開発者は動的に型を調べたり、構造体のフィールドにアクセスしたり、メソッドを呼び出したりすることができます。これは、JSONエンコーディング/デコーディング、ORM、テンプレートエンジンなど、多くのメタプログラミングシナリオで不可欠です。
Goの構造体は、他の構造体を匿名フィールドとして埋め込む(embedding)ことができます。これにより、埋め込まれた構造体のフィールドやメソッドが、あたかも埋め込み元の構造体のフィールドやメソッドであるかのように、直接アクセスできるようになります。この機能はコードの再利用性を高め、簡潔な記述を可能にしますが、同時にフィールド名の衝突という複雑な問題を引き起こす可能性があります。
例えば、ある構造体が二つの異なる構造体を埋め込み、それら二つの埋め込み構造体が偶然にも同じ名前のフィールドを持っている場合、そのフィールドにアクセスしようとすると曖昧さ(ambiguity)が生じます。Go言語の仕様では、このような曖昧なフィールドアクセスはコンパイル時にエラーとなります。reflect
パッケージもまた、このような曖昧なフィールドを検索した場合には、そのフィールドを返すべきではありません。
しかし、このコミット以前のFieldByNameFunc
の実装では、埋め込み構造体を再帰的に探索する際に、既に探索済みの型やその出現回数を正確に追跡できていませんでした。具体的には、ある型が複数の埋め込みパスを通じて到達可能であるにもかかわらず、その「複数回出現している」という情報が次の探索レベルに適切に伝播されなかったため、衝突を誤って見逃し、曖昧なフィールドを返してしまうバグが存在しました。このバグは、reflect
パッケージの正確性と堅牢性を損なうものであり、Issue #4355として報告され、修正が求められていました。
前提知識の解説
Go言語のreflect
パッケージ
reflect
パッケージは、Goプログラムが自身の構造を検査する(リフレクション)ための機能を提供します。これにより、プログラムは実行時に変数や関数の型情報を取得したり、値を動的に操作したりすることができます。主な用途は以下の通りです。
- 型情報の取得: 変数の型、メソッド、フィールドなどの情報を取得します。例えば、
reflect.TypeOf(myVar)
で変数の型情報を取得し、その型が構造体であればNumField()
でフィールド数を、Field(i)
で個々のフィールド情報を取得できます。 - 値の操作: リフレクションを使って、変数の値を読み書きしたり、メソッドを呼び出したりします。
reflect.ValueOf(myVar)
で変数の値を取得し、SetInt()
,Call()
などのメソッドで操作します。ただし、値の変更にはポインタを介したアクセスが必要です。 - 構造体のタグの利用: 構造体のフィールドに付与されたタグ(例:
json:"name"
,xml:"element"
)を読み取り、シリアライゼーションやバリデーションに利用します。StructField.Tag.Get("json")
のようにしてタグの値を取得します。
reflect
パッケージは非常に強力ですが、型安全性を損なう可能性があり、通常の直接的な操作に比べてパフォーマンスオーバーヘッドも伴うため、必要不可欠な場合にのみ使用することが推奨されます。
構造体の埋め込み(Embedding)
Go言語の構造体は、他の構造体を匿名フィールドとして含めることができます。これを「埋め込み」と呼びます。埋め込まれた構造体のフィールドやメソッドは、あたかも埋め込み元の構造体のフィールドやメソッドであるかのように、直接アクセスできます。
例:
type Engine struct {
Horsepower int
}
type Car struct {
Engine // Engine構造体を埋め込み
Brand string
}
func main() {
myCar := Car{Engine: Engine{Horsepower: 200}, Brand: "Toyota"}
fmt.Println(myCar.Horsepower) // CarからEngineのHorsepowerフィールドに直接アクセス
fmt.Println(myCar.Brand)
}
この例では、Car
構造体がEngine
構造体を埋め込んでいるため、myCar.Horsepower
のように直接Engine
のフィールドにアクセスできます。
フィールド名の衝突(Field Name Collisions)
構造体の埋め込みにおいて、異なるパスから同じ名前のフィールドに到達できる場合、フィールド名の衝突が発生します。Goの言語仕様では、このような曖昧なアクセスは許可されません。reflect
パッケージも同様に、曖昧なフィールドを検索した場合には、そのフィールドを返すべきではありません。
例:
type PartA struct {
ID int
}
type PartB struct {
ID int
}
type Product struct {
PartA
PartB
}
func main() {
p := Product{}
// fmt.Println(p.ID) // コンパイルエラー: ambiguous selector p.ID
}
このProduct
構造体では、PartA
とPartB
の両方がID
フィールドを持っているため、p.ID
は曖昧になります。Goコンパイラはこれをエラーとします。reflect
パッケージのFieldByName
やFieldByNameFunc
も、このようなケースではフィールドが見つからない(nil
を返す)ように振る舞うべきです。
技術的詳細
このコミットの技術的詳細は、reflect
パッケージのFieldByNameFunc
メソッドにおける、埋め込み構造体の探索ロジック、特に「型の出現回数(multiplicity/count)」の伝播方法の改善にあります。
FieldByNameFunc
は、指定された関数(match
)を使って構造体のフィールドを探索します。この関数は、構造体のフィールドと、埋め込まれた構造体のフィールドを再帰的に探索します。この探索プロセスでは、各フィールドがどのパスを通じて到達可能か、そしてそのパスが複数存在するかどうかを追跡する必要があります。
バグの核心は、nextCount
というマップの更新ロジックにありました。nextCount
は、次の探索レベルで処理される埋め込み構造体の型が、これまでに何回検出されたかを追跡するためのものです。
修正前のコードでは、埋め込み構造体styp
がnextCount
に既に存在する場合、単にnextCount[styp]++
としていました。これは、その型が複数回出現したことを示すには十分ですが、その「複数回出現している」という状態が、さらに深いレベルの探索に適切に伝播されない可能性がありました。
例えば、以下のような構造体階層を考えます。
Root
がBranch1
とBranch2
を埋め込み、Branch1
とBranch2
が両方ともLeaf
を埋め込んでいる場合。
Root -> Branch1 -> Leaf
Root -> Branch2 -> Leaf
この場合、Leaf
はRoot
から2つの異なるパスで到達可能です。FieldByNameFunc
がBranch1
とBranch2
を処理する際に、Leaf
がnextCount
に追加されるべきです。しかし、もしBranch1
を処理した時点でLeaf
がnextCount
に追加され、次にBranch2
を処理した際にLeaf
が既に存在するためnextCount[Leaf]++
となるだけだと、そのLeaf
が「曖昧である」という情報が、Leaf
の内部フィールドを探索する際に失われる可能性がありました。
修正後のコードでは、この伝播ロジックが強化されています。
-
if nextCount[styp] > 0 { nextCount[styp] = 2; continue }
- これは、
styp
が既にnextCount
に存在する場合(つまり、既に一度検出されている場合)、そのカウントを2
に設定します。2
という値は、その型が「複数回出現している」ことを明確に示します。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要です。これにより、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。
- これは、
-
if count[t] > 1 { nextCount[styp] = 2 }
- これは新たに追加された重要な変更です。現在の探索レベルの型
t
自体が既に曖昧である(つまり、count[t] > 1
)場合、そのt
が埋め込んでいるstyp
もまた、曖昧なパスを通じて到達可能であると見なされます。したがって、styp
のnextCount
も2
に設定されます。これにより、曖昧さの情報が探索の深さに応じて正しく伝播されるようになります。
- これは新たに追加された重要な変更です。現在の探索レベルの型
これらの変更により、FieldByNameFunc
は、複数の埋め込みパスを通じて到達可能なフィールド(つまり、曖昧なフィールド)を正しく識別し、それらを結果として返さないようになります。これは、Goのリフレクションが言語の曖昧性解決ルールに厳密に従うために不可欠です。
コアとなるコードの変更箇所
変更は主に以下の2つのファイルで行われています。
-
src/pkg/reflect/all_test.go
- 新しいテストケースが追加されています。これは、修正が正しく機能することを確認するためのものです。
S14
,S15
,S16
という構造体が定義されています。
ここで、type S14 struct { S15 S16 } type S15 struct { S11 } type S16 struct { S11 }
S11
はX
というフィールドを持つ構造体です。S14
はS15
とS16
を埋め込み、S15
とS16
は両方ともS11
を埋め込んでいるため、S14.X
はS15.S11.X
とS16.S11.X
の両方から到達可能となり、曖昧なフィールドとなります。fieldTests
配列に{S14{}, "X", nil, 0}
というエントリが追加されています。これは、S14
型に対してX
という名前のフィールドを検索した場合、結果がnil
(フィールドが見つからない)になることを期待しています。これは、この修正が意図する「曖昧なフィールドは返さない」という振る舞いを検証します。
-
src/pkg/reflect/type.go
(*structType).FieldByNameFunc
メソッド内のロジックが変更されています。- 具体的には、埋め込み構造体を処理する部分で、
nextCount
マップの更新方法が修正されています。
--- a/src/pkg/reflect/type.go
+++ b/pkg/reflect/type.go
@@ -913,19 +913,22 @@ func (t *structType) FieldByNameFunc(match func(string) bool) (result StructFiel
// Queue embedded struct fields for processing with next level,
// but only if we haven't seen a match yet at this level and only
- // if the embedded types haven't alredy been queued.
+ // if the embedded types haven't already been queued.
if ok || ntyp == nil || ntyp.Kind() != Struct {
continue
}
styp := (*structType)(unsafe.Pointer(ntyp))
if nextCount[styp] > 0 {
- nextCount[styp]++
+ nextCount[styp] = 2 // exact multiple doesn't matter
continue
}
if nextCount == nil {
nextCount = map[*structType]int{}
}
nextCount[styp] = 1
+ if count[t] > 1 {
+ nextCount[styp] = 2 // exact multiple doesn't matter
+ }
var index []int
index = append(index, scan.index...)
index = append(index, i)
コアとなるコードの解説
src/pkg/reflect/type.go
のFieldByNameFunc
メソッド内の変更点に焦点を当てて解説します。このコードブロックは、構造体のフィールドを探索するループ内で、埋め込み構造体(ntyp
がStruct
型の場合)を処理する部分です。
-
if nextCount[styp] > 0 { nextCount[styp] = 2; continue }
styp
は現在処理している埋め込み構造体の型です。nextCount
は、次の探索レベルで処理される埋め込み構造体の型と、それがこれまでに検出された回数を記録するマップです。- 変更前:
nextCount[styp]++
styp
が既に検出されている場合、単にそのカウントをインクリメントしていました。これは、styp
が複数回出現していることを示しますが、その「複数回出現している」という状態が、さらに深いレベルの探索に適切に伝播されない可能性がありました。
- 変更後:
nextCount[styp] = 2
styp
が既に検出されている場合(つまり、nextCount[styp]
が0
より大きい場合)、そのカウントを2
に設定します。これは、その型が「複数回出現している」ことを明確にマークするためのものです。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要であるため、2
という固定値が使われます。これにより、この型を通じてアクセスできるフィールドは曖昧であると判断され、最終的に返されなくなります。continue
は、このstyp
の処理をスキップし、次の埋め込み構造体の探索に進むことを意味します。
-
if count[t] > 1 { nextCount[styp] = 2 }
- この行は新たに追加されたロジックです。
t
は現在の探索レベルで処理している構造体の型です。count[t]
は、現在の構造体t
が、上位のレベルから複数のパスを通じて到達可能であるかどうかを示します(count[t] > 1
であれば曖昧)。- 追加されたロジック: もし現在の構造体
t
自体が既に曖昧なパスを通じて到達可能である場合、そのt
が埋め込んでいるstyp
もまた、曖昧なパスを通じて到達可能であると見なされます。したがって、styp
のnextCount
も2
に設定され、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。これにより、曖昧さの情報が探索の深さに応じて正しく伝播されるようになります。
これらの変更により、FieldByNameFunc
は、埋め込み構造体の探索において、型の出現回数(multiplicity)情報をより正確に伝播できるようになりました。これにより、複数の埋め込みパスを通じて到達可能なフィールド(名前の衝突があるフィールド)を正しく識別し、それらを結果として返さないという、Go言語の仕様に準拠した振る舞いを実現しています。
関連リンク
- Go言語の
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語の構造体の埋め込みに関する公式ドキュメントやチュートリアル(Goのバージョンによってリンクが異なる場合がありますが、
Go embedding
などで検索すると見つかります)
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/aa3880178850ab0525802a48fc4eeadcdbb2c26c
- Go Issue #4355:
reflect: FieldByNameFunc misses collisions
: https://github.com/golang/go/issues/4355 - Go CL 6821094 (Gerrit): https://golang.org/cl/6821094
- Go言語におけるリフレクション、構造体の埋め込み、フィールドのシャドウイングに関する一般的な情報源(Web検索結果より):