ジェネリックはGoのバージョン1.18から登場しました。長い間、Goは保守的な言語と見なされ、シンプルさのためにジェネリックは除外されていましたが、プロジェクトの数が増え、エコシステムが発展するにつれて、汎用的な関数や構造体を書く必要が生じました。これは特にコンテナ構造、コレクションの処理アルゴリズム、およびインフラストラクチャコードにとって重要です。
問題点: ジェネリックが登場する前は、コードの重複や空のインターフェース(interface{})を用いる必要があり、これが型の安全性の喪失やパフォーマンスの低下、デバッグの複雑化を引き起こしていました。
解決策: Goにおけるジェネリックは、関数および型の角括弧内で指定される型パラメータを通じて実装されています。constraintsを使用することで許可される型パラメータを制限でき、型の安全性を損なうことなく汎用的な関数を書くことができます。
コード例:
package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }
主な特徴:
ジェネリックにおける型パラメータTに対して算術演算(+, -, *, /)を使用できますか?
いいえ。Goのコンパイラは、型パラメータが算術演算をサポートしているかどうかわかりません。このため、constraintを指定する必要があります。例として、Go 1.18以降のoperator constraintsを持つインターフェースが必要です。
コード例:
type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }
ジェネリックコンテナは異なる型に対して異なる動作を持つメソッドを含むことができますか?
いいえ。ジェネリック構造体や関数のメソッドはすべての型に対して同じですが、メソッド内でtype switchを使用しない限り異なる動作を持つことはできません。動作はconstraintsを通じて定義されるか、完全にパラメータ化されるべきです。
パッケージレベルで型パラメータを持つ型(generic types)を作成できますか?
はい、Go 1.18以降、ジェネリック関数だけでなく、ジェネリック構造体型も作成できます。
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
コレクション操作のためのライブラリ内でinterface{}を使った汎用的なMap機能が実装されていました。
利点:
欠点:
同じプロジェクトでジェネリックに移行し、インターフェースを通じて制限を設定しました。
利点:
欠点: