💬
Goのジェネリクスの使い方
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の引数と戻り値の型はany
とany[]
なので、型の恩恵を受けられない。
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)
}
下記で実行可能
補足: 型制約
以下のような形で、Tが満たすべき条件を指定できる。
[T int|string] // Tはintかstring
[T ~int] // Tはintを基底型とする型
[T Event] // TはEventインターフェースを満たす型
Discussion