Go言語のジェネリクス
2026/07/28

型パラメータと制約

ジェネリクス(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
}

制約の書き方

制約はインターフェースとして書きます。定義した制約名は、anycmp.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つのどれかに当てはまる型」という和集合です。intint64float64のいずれかを型引数にできます。

~は基底型(underlying type)を指定します。~intは「基底型がintの型」を集合に含めます。type MyInt intNumberを満たすので、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