関数だと通りメソッドだと通らないシグネチャ
同じ構造のシグネチャなのに、関数なら通ってメソッドだと落ちる、という組み合わせが Go 1.27rc3 に存在する。
type A[T any] struct{}
func f[T any](A[T], A[A[T]]) {} // OK
func (A[T]) m(A[A[T]]) {} // NG: instantiation cycle
m を宣言しようとすると実際に出るエラーは「instantiation cycle: T instantiated as A[T]」だ。理由は、型を instantiate するときの扱いにある。**メソッドはインターフェース経由の動的ディスパッチの的になりうるため、型を instantiate した時点でメソッドは eager に全部 instantiate される必要がある。**一方、関数は呼ばれた場所からしか instantiate されない。この違いが連鎖を生む。
A[int] を作る → メソッド m も一緒に作る必要がある → m の引数は A[A[int]] → それも作る必要がある → その m の引数は A[A[A[int]]] → 終わらない。関数 f は呼ばれた場所からしか実体化されないので、この無限連鎖は起きない。この無限ループを防ぐための意図的な制限が monomorphization checker だ。
この経緯は#80109 (2026-06-23, CLOSED/NOT_PLANNED) で griesemer が最小再現に落として説明している。「これは interface 充足の再検査ではなく monomorphization checker 由来。コンパイル中の無限ループを防ぐための意図的な制限」。
ここで重要なのは、これは Go 1.27 で新しく生まれた落とし穴ではないということだ。#80172 (2026-06-26, JasonMing) は同じ症状を、ジェネリクスメソッドを一切使わずに再現した。mrkfrmn のコメントが端的だ。「この例に generic method は無い。M は generic 型の通常メソッド。Go 1.26 の playground でも同じ結果になるので Go 1.27 固有ではない」。実際、go1.26 環境で go.mod の go を 1.18 にしても同じ instantiation cycle エラーが再現することを実測でも確認している。一度 release blocker が付いたが剥がされ、Go 1.28 送りになった。
csgura の反論も鋭い。「generic method は呼ばれるまで instantiate されないのに、なぜ宣言時にエラーになるのか」というものだ。この疑問は理にかなっている。実際、この制限はジェネリクスメソッドはインターフェースを満たさないという妥協点の裏返しになっている。
そして2026-08-12、mrkfrmn による決着はこうだ。
On further thought and discussion with team members, we are unlikely to change this, at least for now.
理由は3点ある。(a) メソッドはインターフェース経由の動的ディスパッチの的になりうるため、型を instantiate するとき eager に全部 instantiate する必要がある。関数はそうならない。(b) ジェネリクスメソッドはインターフェースを満たさないので、理屈の上ではこの制限を緩和できる。(c) しかし緩和すると、chunk() は通らず chunk[_ any]() は通る、という不可解な状態になり混乱を招く。ダミーの型パラメータを足すくらいなら、素直に関数へ昇格させるべきだ、という判断になった。
回避策として csgura が示したのが ダミー型パラメータによる instantiation cycle 回避 だ。type Phantom[T any] = *T というエイリアスを使い、func (Slice[A]) Chunked[_ Phantom[A]](n int) Slice[[]A] のようにダミーの型パラメータを1個足すと、ジェネリクスメソッド扱いになりこの制限を回避して通る(型引数は推論されるので呼び出し側は書かなくてよい)。ただし Go チームはこの手法を推奨していない。
出典
#80109
#80172
Go Release Party 1.27 の発表資料リポジトリ slide/deck.md (cant-other / cycle-why スライドとスピーカーノート)、issues.md §2、memo.md §7。