问题描述
在使用 Go 的泛型时,如果泛型类型存在 constraint,而传入的类型在实现这个 constraint 时使用的是 pointer receiver,那么就会遇到 XXX does not satisfy XXX (method XXX has pointer receiver)
的报错,就比如下面这个例子希望用 Create
函数完成所有创建 Person
的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Person interface {
SetID(id int)
}
type Student struct {
ID int
}
func (p *Student) SetID(id int) {
p.ID = id
}
func Create[T Person](id int) *T {
var person T
person.SetID(id)
return &person
}
这里 Student
用 (p *Student)
实现了 Person
,然而如果用 Create[Student](id)
这种方式调用时,编译会遇到这个报错
1
Student does not satisfy Person (method SetID has pointer receiver)
问题解释
问题就在于这段代码中的 (p *Student)
1
2
3
func (p *Student) SetID(id int) {
p.ID = id
}
在 Go 中会认为是 *Student
实现了 SetID
方法,或者说实现了 Person
interface,而不是 Student
,因此提示 Student
并不满足 Person
。
那一个办法是把实现 interface 传入的改成 value receiver
1
2
3
func (p Student) SetID(id int) { // 传入的 p 类型去掉了 *
p.ID = id
}
这样可以通过编译且正常运行,但问题是变成了值传递后,SetID
并不会作用于传入的那个变量,这个函数也形同虚设。
另一个解决方案是可以把调用函数时改成 Create[*Student](1)
,加上这个 *
,报错也会随之消除。但问题就解决了吗?
再仔细看这个函数在传入类型后会变成什么样
1
2
3
4
5
6
7
// T -> *Student
func Create[T Person](id int) *T {
var person T // var person *Student
person.SetID(id)
return &person
}
这里暂且不论原本的返回类型 *T
会变成 **Student
的问题,这个很容易通过调整返回值类型解决。
核心问题在于第二行我们声明了一个 *Student
类型的指针,但实例化在哪?我们创建了一个空指针,所以在运行时会遇到 runtime error: invalid memory address or nil pointer dereference
。同时由于语言限制,我们手上的 T*
并不能转成 T
然后让我们完成实例化。
那么能不能传入 T
,然后转成指针再调用 interface 的方法呢?
1
2
3
4
5
func Create[T Person](id int) *T {
person := new(T)
person.SetID(id) // 报错
return &person
}
然而编译器又给了一个错误 person.SetID undefined (type *T is pointer to type parameter, not type parameter)
,这个问题在于 SetID
是定义给 Student
的,不是给 Student*
用的。
很遗憾,由于 Go 语言层面的缺陷,在仅使用 T
这一个参数时并不能完成我们想要的东西,如果有办法,请通过网页最下方的邮件告诉我,不甚感激。
解决方案
问题在于用 T
编译器不认 constraint,用 T*
又拿不到 T
进行实例化,那么只能去掉 T
的限制,同时再传入带有限制的 T*
。思路如此,具体实现来说需要定义这么一个 interface
1
2
3
4
type PersonPtr[T any] interface {
*T
Person
}
这个定义了一个指针 interface,第一行这里暂时先去掉了 constraint,允许传入任意类型 T
,然后通过第二行使得这个 interface 允许的类型是且只能是 *T
,让我们能从 T
拿到指针,再通过第三行去保证实现了 Person
这个 interface。
那我们就可以进一步修改函数,将传入的类型改为 PersonPtr
1
2
3
4
5
func Create[Ptr PersonPtr[T]](id int) *T {
var ptr Ptr = new(T)
ptr.SetID(id)
return ptr
}
但这仍然不够,编译器会提示 undefined: T
,因为我们没有定义 T
,所以必须在函数的泛型列表中加上 T
,这个函数只能变为
1
2
3
4
5
func Create[T any, Ptr PersonPtr[T]](id int) *T {
var ptr Ptr = new(T)
ptr.SetID(id)
return ptr
}
调用时就变成了
1
stu := Create[Student, *Student](1)
这样调用真的很丑,但好在 Go 这回终于做了个人,通过类型的自动推导可以自动推导出第二个参数,所以调用时可以简化为
1
stu := Create[Student](1)
这样调用看起来就和谐了许多(虽然背后的实现需要用些难懂的 trick,但我们至少终于实现了 Go 中的泛型与 pointer receiver 的共存…
总结
珍爱生命,远离 Go 的泛型!