構造体リテラルのキーに、埋め込みフィールドを辿るフィールドセレクタが直接指定できるようになった
Changes to the language
概要
これまで構造体リテラル(composite literal)のキーには、対象の構造体型がトップレベルに持つフィールド名しか指定できなかった。Go 1.27からは、フィールド代入で使うフィールドセレクタと同様に、埋め込み(embedded)フィールドを経由したフィールドへの参照もキーとして直接書けるようになった。ただし、埋め込みがポインタ型を経由する場合はこの構文は許可されず、コンパイルエラーになる。
導入経緯
issue #9859 は2014年、「type T struct { E }(EはAフィールドを持つ埋め込み型)のとき、T{A: 1}と直感的に書けず、T{E: E{A: 1}}と冗長に書く必要がある」という指摘として立てられた。以降10年以上、言語変更の優先度は低いまま塩漬けにされていたが、2025年に正式なlanguage change/proposalプロセスへ移され、proposal review committeeで審議された結果、Go 1.27で採用された。
議論のハイライト
- 最大の論点は「埋め込みがポインタ型を経由する場合にどう扱うか」だった。暗黙的にポインタを確保(allocate)する案と、コンパイルエラーにする案が長年比較検討され、非公開型の可視性との相互作用が複雑になることから、最終的にコンパイルエラーとする保守的な案で決着した。
- Robert Griesemerからは「埋め込み型に後からフィールドが追加されると、既存の構造体リテラルの意味が(第三者のパッケージ変更によって)意図せず変わってしまう」という懸念が示されたが、「同じ問題は既にフィールド代入(
x.F = 1)でも起きており、この提案が新たに生む問題ではない」という反論があり、最終的にこの懸念は許容された。 - 仕様上は「構造体リテラルのキーにおける『フィールド名』を『フィールドセレクタ』に一般化する」という形で定式化され、フィールド代入の仕組みと統一的に説明できる設計になった。
- 埋め込みフィールドと、その埋め込みフィールドが持つ昇格(promoted)フィールドの両方を同時に指定した場合は、重複フィールド指定と同様にコンパイルエラーとなる。
使用例
Before
package main
import "fmt"
type E struct {
A int
}
type T struct {
E
}
func main() {
t := T{E: E{A: 1}}
fmt.Println(t)
}
After
package main
import "fmt"
type E struct {
A int
}
type T struct {
E
}
func main() {
t := T{A: 1}
fmt.Println(t)
}
移行時の注意
- 埋め込みがポインタ型(
*Eなど)を経由する場合、このセレクタ構文は使えずコンパイルエラーになる。従来通りE: &E{A: 1}のように明示的に書く必要がある。 go.modのgoディレクティブがGo 1.27未満のモジュールでは、この構文を使うとコンパイルエラーになる(言語バージョンによるゲートあり)。- 埋め込み型に後からフィールドが追加された場合、既存の構造体リテラルが指すフィールドが意図せず変わる可能性がある(フィールド代入における既知のリスクと同様)。
実装解説
型検査はtypes2パッケージの構造体リテラル処理で行われる。キーの解決自体は既存のlookupFieldOrMethodをそのまま使って埋め込み経由のフィールドも探索し、得られたindex(フィールドまでの埋め込みパス)の長さが1より大きい、つまりトップレベル直下ではなく昇格(promoted)フィールドである場合に限り、verifyVersionfによるgo1.27の言語バージョンゲートを通す形で実装されている。埋め込みがポインタ型を経由する(indirect)場合は、バージョンによらず常に「invalid implicit pointer indirection」というコンパイルエラーになる。また埋め込みフィールドとその中の昇格フィールドを同時に指定した場合の重複検出は、フィールドのindexパスを鍵にしたtrie構造を使った判定で行われている。バージョンしきい値go1_27自体はversion.goで定義されている。