本文へスキップ
BecomeCoder

Goコース · 第8章 実務のGo · レッスン34

ジェネリクス ― 型パラメータで共通処理をまとめる

ローカル実施

導入

「同じロジックなのに、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] の部分が型パラメータの宣言です。「Tintfloat64 のどちらか」という**制約(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 を使ってください。