HeadlinesBriefing HeadlinesBriefing.com

Go 1.27 ジェネリックメソッド

Hacker News •
×

Go 1.18 が 2022 年にジェネリクスを導入したとき、関数と構造体にジェネリック型パラメータをもたらしましたが、メソッドは除外されました。Go 1.27 のリリースにより、この長く続いた制限は解除されました。メソッドは、受信構造体に追加することなく、独自の型パラメータを定義できるようになりました。

この変更が導入された理由 グラフノードがジェネリック値を保持するように作成する場合、次のように実装できます:

type Node[T any] struct {

value T

}

Map メソッドを追加して、型 T のノードを別の型 U のノードに変換すると想像してください。Go 1.27 以前は、U を Node 構造体自体に直接追加する必要がありました:

type Node[T any, U any] struct {

value T

}

func (n *Node[T, U]) Map() Node[T, U] {

// ...

}

U を構造体自体に追加することは、U が Map 固有の型パラメータであるため、設計が悪いです。他のメソッドが U を使用しない場合でも、受信者の宣言で U を保持する必要がありました。唯一の回避策は、関数が独自の型パラメータを定義できるため、Map をパッケージレベルの関数として実装することでした。しかし、メソッドはそれができませんでした。これにより、コード API 設計が不自然で非慣用的なものになりました。Go 1.27 から、U は Map メソッドのみで定義できます:

func (n *Node[T]) Map[U any]() Node[U] {

// ...

}

なぜこれほど時間がかかったのか?答えは、Go でジェネリクスがどのように実装されているかにあります。コンパイラは、ジェネリクスを主にモノモーフィゼーションを使用して処理します。これは、ジェネリック構造体や関数のコピーを、それらが使用される各特定の型に対して作成することを意味します。いずれの時点で、抽象的なジェネリクスの概念を直接的な機械コードに変換する必要があります。しかしながら、Go のインターフェースシステムは実行時に動作します。インターフェースパラメータに渡された値の具体的な型は、プログラムの実行中に解決されます。この動的ディスパッチは、コンパイル時に解決されるジェネリクスと衝突します。Map メソッドをインターフェースで宣言した場合、どうなるでしょうか:

type Mapper interface {

Map T any, U any U

}

これを機能させるには、実行時に Just-In-Time コンパイラが必要であるか、すべての可能な型のコピーを事前に作成する必要があります。これにより、バイナリが極端に肥大化します。最終的に、Go チームはジェネリックメソッドをインターフェースから分離することを決定しました。Go 1.27 は具象型のジェネリックメソッドを許可しますが、インターフェースでは許可しません。この区別が、上記の例がコンパイルされない理由と、それに関する議論が長引いた理由です。

出典: Hacker News · 要約:HeadlinesBriefing