我正在学习Go 1.18泛型,我试图理解为什么我在这里遇到了麻烦。长话短说,我正在尝试Unmarshal
一个protobuf,我希望blah
中的参数类型“只是工作”。我已经尽可能地简化了这个问题,这个特定的代码再现了我看到的相同的错误消息:
./prog.go:31:5: cannot use t (variable of type *T) as type stringer in argument to do:
*T does not implement stringer (type *T is pointer to type parameter, not type parameter)
package main
import "fmt"
type stringer interface {
a() string
}
type foo struct{}
func (f *foo) a() string {
return "foo"
}
type bar struct{}
func (b *bar) a() string {
return "bar"
}
type FooBar interface {
foo | bar
}
func do(s stringer) {
fmt.Println(s.a())
}
func blah[T FooBar]() {
t := &T{}
do(t)
}
func main() {
blah[foo]()
}
我意识到我可以通过不使用泛型来完全简化这个示例(即,将示例传递给blah(s stringer) {do(s)}
。但是,我想知道为什么会发生错误。
我需要对这段代码做什么修改,才能创建T
的示例,并将该指针传递给需要特定方法签名的函数?
2条答案
按热度按时间pepwfjgg1#
在您的代码中,约束
FooBar
和stringer
之间没有关系。此外,所述方法在指针接收器上实施。对于你的程序,一个快速而肮脏的修复方法是简单地 * Assert *
*T
确实是一个stringer
:Playground:https://go.dev/play/p/zmVX56T9LZx
但这放弃了类型安全性,并可能在运行时发生死机。为了保持编译时的类型安全,另一个稍微保留程序语义的解决方案是:
这是怎么回事
首先,类型参数和它的约束之间的关系不是恒等的:
T
不是FooBar
。您不能像使用FooBar
那样使用T
,因此*T
肯定不等同于*foo
或*bar
。所以当你调用
do(t)
时,你试图将一个**类型*T
**传递给一个需要stringer
的东西,但是T
,不管是否有指针,只是在它的类型集中没有固有的a() string
方法。步骤1:将方法
a() string
添加到FooBar
接口中(通过嵌入stringer
):但这还不够,因为现在你的类型都没有真正实现它。两者都在指针接收器上声明方法。
步骤2:将联合体中的类型更改为指针:
这个约束现在起作用了,但是您还有另一个问题。当约束没有核心类型时,不能声明复合文字。所以
t := T{}
也是无效的。我们将其更改为:现在编译了,但是
t
实际上是指针类型的零值,所以它是nil
。你的程序不会崩溃,因为方法只是返回一些字符串文字。如果你还需要初始化指针所引用的内存,在
blah
中,你需要知道基本类型。第3步:添加
T foo | bar
作为一个类型参数,并将签名更改为:完了?还没。转换
U(&t)
仍然无效,因为U
和T
的类型集不匹配。现在需要在T
中参数化FooBar
。第4步:基本上,您将
FooBar
的union提取到一个类型参数中,以便在编译时其类型集将仅包含以下两种类型中的一种:现在可以使用
T foo | bar
示例化约束,保留类型安全性,指针语义并将T
初始化为非空值。图纸:
Playground:https://go.dev/play/p/src2sDSwe5H
如果你可以用指针类型示例化
blah
,或者更好地传递参数给它,你可以删除所有中间的技巧:hwamh0ep2#
除了上面的答案,还有两种方法可以解决这个问题。在这两种情况下,FooBar类型必须是:
第一个:
创建此通用New函数。它与内置的new func功能相同,但可以轻松地作为函数参数传递。
修改blah函数以接受并使用_new函数。此函数负责创建FooBar示例。
修改主函数:
基本上,我们将内置的新函数注入到泛型函数中。
Full solution on a go playground
第二个:
我们可以使用反射来示例化隐藏在接口后面的指针类型的变量:
blah和main函数是:
Full solution on a go playground