型パラメータと制約
関数や型の名前の直後に角括弧で型パラメータと制約を宣言することで、具体的な型に依存しない汎用的なコードを記述できます。たとえば[T any]と書くと、Tが型パラメータ、anyが制約を表します。型パラメータは呼び出し時やインスタンス化時にintなどの具体的な型に置き換わります。
制約は、型引数として受け入れ可能な型の集合を定めます。型パラメータに対して実行できる演算や呼び出せるメソッドも制約によって決まります。
func Min[T any](a, b T) T {
if a < b { // コンパイルエラー: Tがanyでは<が使えない
return a
}
return b
}順序比較演算子<や>を使うには、順序付けが可能な型を表す制約が必要です。標準ライブラリのcmpパッケージが提供するcmp.Orderedを指定します。
import "cmp"
func Min[T cmp.Ordered](a, b T) T {
if a < b {
return a
}
return b
}ジェネリックな関数を使用する際は、型パラメータに具体的な型引数を渡してインスタンス化します。関数の呼び出しでは引数の型から型引数が推論されるため、通常は型引数の指定を省略できます。
n := Min(3, 5) // 引数からTがintと推論される
m := Min[int](3, 5) // 角括弧で型引数を明示的に指定型引数はコンパイル時に確定します。インターフェースのように実行時に動的型が変化するのではなく、静的な型として扱われます。
制約の定義
型制約はインターフェースとして定義します。
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](xs []T) T {
var total T
for _, x := range xs {
total += x
}
return total
}|は型集合の和集合を表します。チルダ記号~は基底型を指定し、~intと書くことで基底型がintであるすべての型を集合に含めます。int自身に加え、type MyInt intのように新しく定義した型もNumberを満たすため、Sum([]MyInt{1, 2, 3})のように呼び出せます。
any
組み込みのanyは、すべての型を許容する制約です。
comparable
組み込みのcomparableは、等値比較演算子==および!=が使用できる型に限定する制約です。マップのキーとして使う型パラメータなどに指定します。制約としてのみ使用でき、変数やフィールドの型としては宣言できません。
ジェネリック型
構造体などの型定義で型パラメータを宣言できます。定義した型パラメータは、フィールドやメソッドレシーバで使用します。
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) {
s.items = append(s.items, v)
}
var s Stack[int]
s.Push(10)
// s.Push("text") // コンパイルエラー: stringはintに代入できないStack[int]のように型引数を指定すると、フィールドの[]Tやメソッド引数のTがすべてintに置き換わります。
ジェネリックメソッド
Go言語のバージョン1.27以降では、メソッド名の直後にも独自の型パラメータを宣言できます。レシーバの型パラメータとは独立した型パラメータを追加できるため、別の型へ変換する処理などをパッケージレベルの関数に切り出すことなくその型のメソッドとして定義できます。
func (s Stack[T]) Map[U any](f func(T) U) Stack[U] {
items := make([]U, len(s.items))
for i, v := range s.items {
items[i] = f(v)
}
return Stack[U]{items: items}
}渡す関数の引数と戻り値から型引数Uが推論されます。
var numbers Stack[int]
numbers.Push(1)
numbers.Push(2)
// intのStackからboolのStackへ変換
flags := numbers.Map(func(v int) bool {
return v > 0
})