メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

新機能

構造体リテラルのキーに、埋め込みフィールドを辿るフィールドセレクタが直接指定できるようになった

Changes to the language

この項目の注釈は AI により生成されており、誤りを含む場合があります。
使用例のコンパイル検証: 検証済み

概要

これまで構造体リテラル(composite literal)のキーには、対象の構造体型がトップレベルに持つフィールド名しか指定できなかった。Go 1.27からは、フィールド代入で使うフィールドセレクタと同様に、埋め込み(embedded)フィールドを経由したフィールドへの参照もキーとして直接書けるようになった。ただし、埋め込みがポインタ型を経由する場合はこの構文は許可されず、コンパイルエラーになる。

導入経緯

issue #9859 は2014年、「type T struct { E }(EAフィールドを持つ埋め込み型)のとき、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.modgoディレクティブがGo 1.27未満のモジュールでは、この構文を使うとコンパイルエラーになる(言語バージョンによるゲートあり)。
  • 埋め込み型に後からフィールドが追加された場合、既存の構造体リテラルが指すフィールドが意図せず変わる可能性がある(フィールド代入における既知のリスクと同様)。

実装解説

型検査はtypes2パッケージの構造体リテラル処理で行われる。キーの解決自体は既存のlookupFieldOrMethodをそのまま使って埋め込み経由のフィールドも探索し、得られたindex(フィールドまでの埋め込みパス)の長さが1より大きい、つまりトップレベル直下ではなく昇格(promoted)フィールドである場合に限り、verifyVersionfによるgo1.27の言語バージョンゲートを通す形で実装されている。埋め込みがポインタ型を経由する(indirect)場合は、バージョンによらず常に「invalid implicit pointer indirection」というコンパイルエラーになる。また埋め込みフィールドとその中の昇格フィールドを同時に指定した場合の重複検出は、フィールドのindexパスを鍵にしたtrie構造を使った判定で行われている。バージョンしきい値go1_27自体はversion.goで定義されている。

関連リンク