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

Go Proposal Weekly Digest

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

新機能

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

Changes to the language

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

概要

Go 1.27から、メソッド宣言が関数宣言と同様に独自のType Parameterを宣言できるようになった。これまでジェネリクスは関数にのみ許され、メソッドはレシーバ型が持つType Parameterを利用できるだけで、メソッド自身が新たなType Parameterを宣言することはできなかった。この非対称性を取り除き、「メソッドはレシーバを持つ関数である」という構文上の対称性を回復する変更である。

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

導入経緯

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

詳しい経緯はIssue #77273を参照。関連する既存の要望としてIssue #49085(900件超のリアクションを集めた2021年10月提出のIssue)と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らは、関数型同一性の既存規則からの自然な帰結であり新たな制限ではないと説明している。
  • 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=139, signature.go;l=142)。今回の変更はこの既存の型モデルに新しい入力経路を追加するもので、TypeParams() がメソッドについても非nilになりうるようになった。

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

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

インターフェイス実装判定は unify.go の型ユニフィケーション処理で行われ、候補メソッドの Signature().TypeParams() が非nilであれば無条件に不一致として扱い、インターフェイス実装の可能性を排除している(unify.go;l=549)。このコードにより、提案文書にある「ジェネリックなコンクリートメソッドはインターフェイスメソッドを満たさない」という制約が型システムレベルで保証されている。

関連リンク