導入
「同じロジックなのに、int 版と string 版で関数を2つ書く」――そんな重複を防ぐために、Go 1.18(2022年)から ジェネリクス(型パラメータ) が追加されました。「どんな型でも使える処理」を1つの関数・型として書けます。
説明
たとえば、スライスの中から最大値を探す処理を、型ごとに別々の関数として書くと重複が生まれます。
func MaxInt(nums []int) int {
max := nums[0]
for _, n := range nums {
if n > max {
max = n
}
}
return max
}
func MaxFloat64(nums []float64) float64 {
max := nums[0]
for _, n := range nums {
if n > max {
max = n
}
}
return max
}
ジェネリクスを使うと、この2つを型パラメータでまとめて1つにできます。
package main
import "fmt"
// T は「比較できる型ならなんでもよい」という制約
func Max[T int | float64](nums []T) T {
max := nums[0]
for _, n := range nums {
if n > max {
max = n
}
}
return max
}
func main() {
fmt.Println(Max([]int{3, 7, 2})) // 7
fmt.Println(Max([]float64{1.5, 2.8, 0.3})) // 2.8
}
Max[T int | float64](nums []T) T…[T int | float64]の部分が型パラメータの宣言です。「Tはintかfloat64のどちらか」という**制約(constraint)**を表します。- 呼び出すときに
Tを明示しなくても、渡した引数([]intか[]float64か)からGoが自動で判断してくれます。 - こうして書いた
Maxは、intのスライスにもfloat64のスライスにも、そのまま使い回せます。
型を限定しない「どんな型でもよい」場合は any という組み込み制約を使います。逆に「== で比較できる型に限る」場合は comparable を使います。
// S はどんな型のスライスでも受け取れる(要素の値そのものは使わない処理)
func First[S any](items []S) S {
return items[0]
}
// K は比較可能な型(マップのキーになれる型)に限定
func Contains[K comparable](items []K, target K) bool {
for _, item := range items {
if item == target {
return true
}
}
return false
}
標準ライブラリの slices パッケージや maps パッケージ(Go 1.21以降)は、こうしたジェネリクスを使って「どんな型のスライス・マップにも使える」共通関数を提供しています。
ジェネリクスは便利な一方、乱用すると読みにくくなるという側面もあります。まずは「型ごとに同じロジックを何度も書いている」と気づいたときに使う、という程度に留めておくのが実務での付き合い方です。
このサイトの学習用シミュレータは型パラメータの構文([T any])に対応していないため、実際に動かすには手元の Go 環境(Go 1.18以降)や Go Playground を使ってください。