Go1.20 の頃の話なんですけど。
お困りごと
こんな感じでレシーバがポインタで宣言されてて、かつ関数の中でレシーバのフィールドを触ってるケース。
type Hogeable interface {
Hoge()
}
type HogeUser1 struct{ s string }
type HogeUser2 struct{ s string }
type HogeUser3 struct{ s string }
func (h *HogeUser1) Hoge() {
h.s = "HogeUser1"
fmt.Println(h.s)
}
func (h *HogeUser2) Hoge() {
h.s = "HogeUser2"
fmt.Println(h.s)
}
func (h *HogeUser3) Hoge() {
h.s = "HogeUser3"
fmt.Println(h.s)
}
これを実体宣言して呼び出す Generics 関数を用意したい、となったとき。
ナイーブに Generics にすると、T
がそもそもポインタ型なので実体が作れなくて死んでしまう。
func BadCallHoge[T Hogeable]() {
var hoge T
hoge.Hoge()
}
func main() {
BadCallHoge[*HogeUser1]()
BadCallHoge[*HogeUser2]()
BadCallHoge[*HogeUser3]()
}
ぬるぽ
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x108ddb8]
ちなみに型引数に実体型を渡すとコンパイルエラーになる。レシーバがポインタで宣言されてるため。
BadCallHoge[HogeUser1]()
// HogeUser1 does not satisfy Hogeable (method Hoge has pointer receiver)
改善策
実体型を any
で渡して、それに interface
で制約つけて、キャストして呼び出してやるといけるらしい。
func CallHoge[T any, PT interface {
Hogeable
*T
}]() {
var hoge T
PT(&hoge).Hoge()
}
func main() {
CallHoge[HogeUser1]()
CallHoge[HogeUser2]()
CallHoge[HogeUser3]()
}
ちなみに any
のところは union でもおk、というか union で絞った方がいいかも。
T HogeUser1 | HogeUser2 | HogeUser3
なお、これが正しいやり方なのかはわからん。
全体のコード
package main
import (
"fmt"
)
type Hogeable interface {
Hoge()
}
type HogeUser1 struct{ s string }
type HogeUser2 struct{ s string }
type HogeUser3 struct{ s string }
func (h *HogeUser1) Hoge() {
h.s = "HogeUser1"
fmt.Println(h.s)
}
func (h *HogeUser2) Hoge() {
h.s = "HogeUser2"
fmt.Println(h.s)
}
func (h *HogeUser3) Hoge() {
h.s = "HogeUser3"
fmt.Println(h.s)
}
func CallHoge[T any, PT interface {
Hogeable
*T
}]() {
var hoge T
PT(&hoge).Hoge()
}
func main() {
CallHoge[HogeUser1]()
CallHoge[HogeUser2]()
CallHoge[HogeUser3]()
}
おわり。