スキップしてメイン コンテンツに移動

Go1.18ジェネリクスで型安全

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 などの概念を理解し、汎用関数を積極的に導入することで、より堅牢で再利用性の高いコードを書けるようになるでしょう。

この記事はAIによって作成されました。

コメント