これは何?
最近 Go を書く機会があって。
generics 関数内で引数型のインスタンスを作れなくて苦労したので、やりたかったこと、書いてみて駄目だったこと、うまく行ったコードを記す。
やりたいこと
インターフェイス Hoge
があり。Hoge
を実装するクラスが FugaType
他多数あり。
hogeCreator := CreateHogeCreator[FugaType](arg)
hoge := hogeCreator()
のように使う generics な関数 CreateHogeCreator
が書きたかった。
トライアル
まずは素朴に
func CreateHogeCreator[T Hoge](arg HogeInitArg) func() Hoge {
return func() Hoge {
v := T{} // invalid composite literal type T compiler(InvalidLit)
v.Init(arg)
return v
}
}
と書くとエラー。T は インターフェイスかもしれないんだから、そりゃ作れない。
T がなにかのポインタであるべきなのかなにかであるべきなのかもごっちゃになっている。駄目。
しかしその辺りを整理して、呼ぶ側の調整をしても全然うまくいかない。
必要なのは「Hoge
を実装している構造体(インターフェイスではない)」という制約。そうじゃないと T{}
って書けない。
正解
正解はこう。
type HogeConstraint[X any] interface {
Hoge
*X
}
func CreateHogeCreator[T any, PT HogeConstraint[T]](arg HogeInitArg) func() Hoge {
return func() Hoge {
var v T
pv := PT(&v)
pv.Init(arg) // (&v).Init(arg) だとエラー
return pv // return &v だとエラー
}
}
こんなの思いつかないよ。
この定義で、 hogeCreator := CreateHogeCreator[FugaType](arg)
と呼べるようになる。
ちなみに。
単にファクトリ関数なら hoge := CreateHoge(&FooType{}, arg)
のようにすればそもそも generics が不要になる。
しかし。
CreateHogeCreator
のように、generics 内で型引数で指定された型のインスタンスを作りたいことはまあまああって、その場合このようなコードが必要になる。
まとめ
go の generics、今のところすごく難しい。
私の理解が足りないからそう感じるのか、本当に難しいのかはまだわからない。
下記の記事のおかげで書けた。ありがとうございます。