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

Go Proposal Weekly Digest

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

新機能

メソッド宣言が独自のType Parameterを持てるようになり、ジェネリックメソッドが導入された

Changes to the language

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

概要

Go 1.27から、メソッド宣言が関数宣言と同様に独自のType Parameterを宣言できるようになった。これまでジェネリクスは関数にのみ許され、メソッドはレシーバ型が持つType Parameterを利用できるだけで、メソッド自身が新たなType Parameterを宣言することはできなかった。この非対称性を取り除き、「メソッドはレシーバを持つ関数である」という構文上の対称性を回復する変更である。標準ライブラリでは、math/rand/v2にジェネリック関数N[Int intType](Int) Intに対応する(*Rand) N[Int intType](Int) Intメソッドが新たに追加された。

なお、インターフェイスのメソッドはType Parameterを宣言できず、ジェネリックなコンクリートメソッドがインターフェイスメソッドを実装(満たす)することもできない。

導入経緯

提案はIssue #77273として提出され、2026年1月28日のProposal Review Meetingで議題に上がり"active"となった。技術的な整理を経て2月11日に"likely_accept"に移行し、コミュニティから寄せられた「レシーバのType Parameterをメソッド側のType Parameterの制約に使えるか」という疑問が2月18日の会議で議論され、既存spec上の制約(型パラメータはunion termとして使えない)により不可であることが明確化された後、2026年2月25日にAustin Clementsが正式に"accepted"を宣言した。

関連する既存の要望として、900件超のリアクションを集めた2021年10月提出のIssue #49085と、2022年2月提出のIssue #50981がある。

議論タイムライン

  • 2026-01-28: Proposal Review Meetingで議題に追加され"active"に。
  • 2026-02-11: "likely_accept"に移行。
  • 2026-02-18: レシーバType ParameterをメソッドType Parameterの制約に使えるかという疑問を議論。
  • 2026-02-25: Austin Clementsが正式に"accepted"を宣言。

議論のハイライト

  • 提案者Robert Griesemerは「コンクリートメソッドはインターフェイスを実装する手段としてだけでなく、型の名前空間の下でコードを整理する手段としても有用である」という視点の転換を提示した。Goの公式FAQには長年「genericメソッドを追加する予定はない」と明記されていたが、ジェネリクス導入後の実運用経験を踏まえて方針転換した。
  • ジェネリックなコンクリートメソッドがインターフェイスを満たせないことについて、Ian Lance Taylorは「現在はどのメソッドもインターフェイス実装に貢献しうるが、この提案では一部のメソッドはできなくなる」と直交性の観点から懸念を表明した。一方でMeroviusらは、これは新たな制約ではなく、関数型同一性の既存規則(型パラメータの有無も含めて型が一致しなければならない)から導かれる自然な帰結であると説明している。
  • func (X) Read[T any]([]T) (int, error)io.Readerを実装しない点は多くのコメントで議論の的になった。同様の制限はRustのtraitオブジェクトにも存在することが指摘され、適切なコンパイルエラーメッセージで対処可能との見解が示された。
  • math/rand/v2.Rand型がN関数に対応するメソッドを持てない、という標準ライブラリの非対称性が具体例として繰り返し挙げられた。
  • リフレクション経由でジェネリックメソッドにアクセスすることはできない。未インスタンス化のジェネリック関数がreflectパッケージから参照できないのと同じ理由による。

使用例

Before

package main

import "fmt"

type Set struct {
	items map[any]struct{}
}

func NewSet() *Set {
	return &Set{items: make(map[any]struct{})}
}

// 型パラメータをメソッドに直接持たせられないため、パッケージレベルの
// ジェネリック関数として書くしかなかった。
func AddToSet[T any](s *Set, v T) {
	s.items[v] = struct{}{}
}

func main() {
	s := NewSet()
	AddToSet(s, 42)
	AddToSet(s, "hello")
	fmt.Println(len(s.items))
}

After

package main

import "fmt"

type Set struct {
	items map[any]struct{}
}

func NewSet() *Set {
	return &Set{items: make(map[any]struct{})}
}

// メソッド自身がType Parameterを宣言できる。
func (s *Set) Add[T any](v T) {
	s.items[v] = struct{}{}
}

func main() {
	s := NewSet()
	s.Add(42)
	s.Add("hello")
	fmt.Println(len(s.items))
}

移行時の注意

既存コードへの影響はなく、制約を緩和するだけの後方互換な変更である。ただし以下の点に注意が必要:

  • ジェネリックメソッドはインターフェイスを満たせない。たとえばfunc (*Reader) Read[E any]([]E) (int, error)は、シグネチャの見た目が近くてもio.Readerを実装しない。
  • メソッド式メソッド値を経由してジェネリックメソッドを参照すると、得られる関数はジェネリック関数になり、他のジェネリック関数と同様に呼び出し前のインスタンス化(または型推論)が必要になる。
  • ジェネリックメソッドはリフレクション(名前・インデックスいずれの経由でも)からアクセスできない。
  • go/typesなどを扱うツールがこれまで「メソッドはレシーバのType Parameterしか持たない」という前提を置いていた場合、Signature.TypeParams()がメソッドについても非nilになるケースへの対応が必要になる。

実装解説

go/types.Signatureは元々、レシーバ由来のType Parameterを返すRecvTypeParams()と、メソッド/関数自身が宣言するType Parameterを返すTypeParams()を別々のアクセサとして持っていた(signature.go;l=138, signature.go;l=141)。今回の変更はこの既存の型モデルに新しい入力経路を追加するもので、TypeParams()がメソッドについても非nilになりうるようになった。

構文解析側では、cmd/compile/internal/syntaxパーサのfuncDeclOrNilがレシーバの有無によらず同じfuncTypeヘルパーで型パラメータリストをパースしている(parser.go;l=799, parser.go;l=825)。funcType自体は、呼び出し元がcontext引数を空文字列で渡す限り[...]の型パラメータリストを無条件に受理する(parser.go;l=1451, parser.go;l=1460)。つまり関数宣言とメソッド宣言の構文はすでに統一的に扱われている。一方、インターフェイスのメソッド宣言をパースする箇所は、型パラメータらしきものを検出すると明示的に"interface method must have no type parameters"エラーを出す(parser.go;l=1812)。

型チェッカー側では、cmd/compile/internal/types2resolver.goが、レシーバを持つ宣言(メソッド)が自身のType Parameterを持つ場合にgo1_27版のみ許可するバージョンチェックを行っている(resolver.go;l=460)。つまりこの機能はGo言語バージョン(go.modgoディレクティブ相当)によるゲーティングがかかっている。

インターフェイス実装判定はunify.goの型ユニフィケーション処理で行われ、候補メソッドのSignature().TypeParams()が非nilであれば無条件に不一致として扱い、インターフェイス実装の可能性を排除している(unify.go;l=546)。コード中のコメントには「ジェネリックメソッドはインターフェイスメソッドを決して満たさないため、ym自身のType Parameterを推論変数へユニファイしようとするより先に失敗させる」との説明があり、提案文書にある「ジェネリックなコンクリートメソッドはインターフェイスメソッドを満たさない」という制約が型システムレベルで保証されていることが確認できる。

関連リンク