型パラメータと制約
ジェネリクス(Go 1.18以降)は、関数や型定義に型パラメータを書き、具体の型に当てはめて使い回す仕組みです。Tのような名前は、関数を呼び出すときや型引数を与えるときに、intなどの具体型に置き換わります。型パラメータは単独では書かず、角括弧内で制約とセットになります。[T any]のTが型パラメータ、anyが制約です。
型パラメータの構文
func Min[T cmp.Ordered](a, b T)やtype Stack[T any] structのように、関数・型どちらも名前の直後の[T 制約]に型パラメータと制約を並べて書きます。
制約と演算
制約は型引数として許容する型の集合を表し、関数内で使える演算やメソッドも制約で決まります。制約をanyにすると型引数は何でもよい一方、必要な演算が使えないことがあります。
func Min[T any](a, b T) T {
if a < b { // コンパイルエラー: Tがanyでは<が使えない
return a
}
return b
}比較には<や>が使える型向けの制約が必要です。
import "cmp"
func Min[T cmp.Ordered](a, b T) T {
if a < b {
return a
}
return b
}制約の書き方
制約はインターフェースとして書きます。定義した制約名は、anyやcmp.Orderedと同じく、型パラメータの角括弧内に書きます。
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 | ~int64 | ~float64は「3つのどれかに当てはまる型」という和集合です。int、int64、float64のいずれかを型引数にできます。
~は基底型(underlying type)を指定します。~intは「基底型がintの型」を集合に含めます。type MyInt intもNumberを満たすので、Sum([]MyInt{1, 2, 3})のように呼び出せます。intそのものも~intに含まれます。
any
組み込みのanyは制約なしです。Min[T any]のように、どの型でも型引数にできますが、関数内で自由に演算できるわけではありません。
comparable
組み込みのcomparableは==と!=が使える型向けです。マップのキー型などに使います。
cmp.Ordered
cmp.Orderedは<や>などの順序比較が使える型向けです。Minの修正例ではこの制約を使いました。
制約をanyに緩めすぎると必要な演算やメソッドが使えずコンパイルエラーになるため、「この型パラメータで何をするか」に合わせて絞ります。
ジェネリック型
型定義では、フィールドやメソッドのレシーバ型に同じ型パラメータを書きます。
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) {
s.items = append(s.items, v)
}Stack[int]のように型引数を与えると、フィールドの[]Tもメソッドの引数もintに置き換わった型になります。
インスタンス化と型推論
型引数は呼び出しや変数宣言のときに与えます。関数呼び出しでは、引数の型から型パラメータを推論できることがあります。
n := Min(3, 5) // Tはintと推論
var s Stack[int]推論できない場合はMin[int](3, 5)のように明示します。インターフェースの動的型付けとは異なり、型引数はコンパイル時に決まり、型引数ごとのコードが展開されます。
type MyInt intのように型定義した型も、cmp.Orderedなどの制約を満たせば同じ型パラメータで使えます。
Min(3, 5) // Tはint
Min(MyInt(3), MyInt(7)) // TはMyInt