Go1.18ジェネリクスで型安全
Goジェネリクスの概要
Go1.18で導入された新機能「Goジェネリクス」は、型安全性を保ちながらコード再利用を実現するための強力なツールです。従来のGoでは、インターフェースを使って汎用的な処理を行うことが多かったですが、インターフェースは型情報を失いやすく、実行時に型アサーションが必要になるケースが多いです。ジェネリクスを使うことで、コンパイル時に型が確定し、実行時のオーバーヘッドを減らすことができます。
Goジェネリクスは、Type Parameters(型パラメータ)という概念を導入し、関数や構造体に対して「T」というプレースホルダーを設定します。これにより、同じロジックを複数の型に対して再利用できるようになります。
Type Parametersとany, comparable
Type Parametersは、関数や構造体の宣言時に角括弧で型パラメータを指定します。例えば、以下のように書くと、Tは任意の型を受け取ります。
func Identity[T any](value T) T {
return value
}
ここで使われている any は、Go 1.18で導入された型パラメータの制約で、すべての型を許容します。対照的に comparable は、比較演算子(==, !=)が使える型に限定します。例えば、マップのキーとして使える型は comparable である必要があります。
制約を組み合わせることで、より細かい型安全性を確保できます。例えば、以下のように書くと、Tは数値型(int, float64 など)に限定されます。
type Number interface {
int | int64 | float64 | float32
}
func Sum[T Number](a, b T) T {
return a + b
}
汎用関数で実現する型安全性とコード再利用
汎用関数(Generic Function)は、Goジェネリクスの核心です。汎用関数を使うことで、型安全性を保ちながら、同じアルゴリズムを複数の型に対して適用できます。以下は、スライスをソートする汎用関数の例です。
func SortSlice[T comparable](s []T) []T {
// ここでは簡易的に挿入ソートを実装
for i := 1; i < len(s); i++ {
key := s[i]
j := i - 1
for j >= 0 && s[j] > key {
s[j+1] = s[j]
j--
}
s[j+1] = key
}
return s
}
この関数は、comparable 制約のおかげで、比較演算子が使える任意の型に対して動作します。実際に使う際は、以下のように呼び出します。
ints := []int{5, 3, 8, 1}
sortedInts := SortSlice(ints)
floats := []float64{3.14, 2.71, 1.41}
sortedFloats := SortSlice(floats)
汎用関数を活用することで、コードの重複を減らし、メンテナンス性を向上させることができます。また、型安全性が保証されるため、ランタイムエラーのリスクも低減します。
Go1.18の新機能であるジェネリクスは、Go実践において不可欠なツールとなりつつあります。Type Parameters、any、comparable などの概念を理解し、汎用関数を積極的に導入することで、より堅牢で再利用性の高いコードを書けるようになるでしょう。
コメント
コメントを投稿