💬

Goのジェネリクスの使い方

2024/10/19に公開

Genericsを用いない場合の困りごと

mapからKeyを取り出すgetKeys関数の実装を考える。
まず、Keyの型、Valueの型が何でもいいgetAnyKeys()を実装する

func getAnyKeys(m any) ([]any, error) {
	switch t := m.(type) {
	default:
		return nil, errors.New("未対応の型が与えられました")
	case map[string]int:
		var keys []any
		for k := range t {
			keys = append(keys, k)
		}
		return keys, nil
	}
}

これを色々な型に対応させるためにはcase文をコピペすればいいが、getKeysの引数と戻り値の型はanyany[]なので、型の恩恵を受けられない。

Genericsの使用

Kがcomparableという型制約を満たし、Vはanyという方のmap[K]Vに対応するように定義できる。

func getGenericsKeys[K comparable, V any](m map[K]V) []K {
	var keys []K
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

こうすると、実装がシンプルになる上に、引数の型に応じて戻り値の方が決定される。

比較

func main() {
	m := make(map[string]int, 0)
	m["one"] = 1
	m["two"] = 2
	// var anyKeys = []string{}とするとコンパイルエラー(getAnyKeysの戻り値は常に[]any)
	anyKeys, err := getAnyKeys(m)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("getAnyKeys: ", anyKeys)

	// genericsKeysの型は今回は[]string{}に決まっているため、コンパイルできる
	var genericsKeys = []string{}
	genericsKeys = getGenericsKeys(m)
	fmt.Println("getGenericsKeys: ", genericsKeys)
}

下記で実行可能
https://go.dev/play/p/W8wp4n4Pe7S

補足: 型制約

https://go.dev/ref/spec#Type_constraints

以下のような形で、Tが満たすべき条件を指定できる。

[T int|string]  // Tはintかstring
[T ~int]        // Tはintを基底型とする型
[T Event]       // TはEventインターフェースを満たす型

Discussion