我正在学习 Go 1.18 泛型,我试图理解为什么我在这里遇到麻烦。长话短说,我正在尝试使用Unmarshalprotobuf,并且希望参数类型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)
Run Code Online (Sandbox Code Playgroud)
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]()
}
Run Code Online (Sandbox Code Playgroud)
我意识到我可以通过不使用泛型来完全简化这个示例(即将实例传递给blah(s stringer) {do(s)})。但是,我确实想了解为什么会发生错误。
我需要对此代码进行哪些更改,以便我可以创建一个实例T并将该指针传递给需要特定方法签名的函数?
FooBar在您的代码中,约束和之间没有关系stringer。此外,这些方法是在指针接收器上实现的。
对你设计的程序的一个快速而肮脏的修复就是简单地断言这*T确实是一个stringer:
func blah[T FooBar]() {
t := new(T)
do(any(t).(stringer))
}
Run Code Online (Sandbox Code Playgroud)
游乐场:https://go.dev/play/p/zmVX56T9LZx
但这会放弃类型安全,并且可能会在运行时出现恐慌。为了保持编译时类型安全,另一种在某种程度上保留程序语义的解决方案是:
type FooBar[T foo | bar] interface {
*T
stringer
}
func blah[T foo | bar, U FooBar[T]]() {
var t T
do(U(&t))
}
Run Code Online (Sandbox Code Playgroud)
那么这是怎么回事呢?
首先,类型参数与其约束之间的关系不是identity:T 不是 FooBar。你不能T像以前那样使用FooBar,因此*T绝对不等于*fooor *bar。
因此,当您调用 时do(t),您正在尝试将类型*T传递到需要 的对象中stringer,但是,无论是否为指针,其类型集中T本质上都没有该方法。a() string
步骤1:将方法添加到接口a() string中FooBar(通过嵌入stringer):
type FooBar interface {
foo | bar
stringer
}
Run Code Online (Sandbox Code Playgroud)
但这还不够,因为现在没有一个类型真正实现它。两者都在指针接收器上声明了方法。
步骤2:将union中的类型更改为指针:
type FooBar interface {
*foo | *bar
stringer
}
Run Code Online (Sandbox Code Playgroud)
这个约束现在有效,但你还有另一个问题。当约束没有核心类型时,您无法声明复合文字。所以t := T{}也是无效的。我们将其更改为:
func blah[T FooBar]() {
var t T // already pointer type
do(t)
}
Run Code Online (Sandbox Code Playgroud)
现在可以编译了,但t实际上是指针类型的零值,所以它是nil. 您的程序不会崩溃,因为这些方法只返回一些字符串文字。
如果您还需要初始化指针引用的内存,那么blah您需要了解内部的基本类型。
第 3 步:因此,您添加T foo | bar一种类型参数,并将签名更改为:
func blah[T foo | bar, U FooBar]() {
var t T
do(U(&t))
}
Run Code Online (Sandbox Code Playgroud)
完毕?还没有。转换U(&t)仍然无效,因为两者的类型集U不T匹配。您现在需要FooBar在 中进行参数化T。
步骤 4:基本上,您将FooBar的 union 提取到类型参数中,以便在编译时其类型集将仅包含以下两种类型之一:
type FooBar[T foo | bar] interface {
*T
stringer
}
Run Code Online (Sandbox Code Playgroud)
现在可以使用 实例化约束T foo | bar,保留类型安全、指针语义并初始化T为非零。
func (f *foo) a() string {
fmt.Println("foo nil:", f == nil)
return "foo"
}
func main() {
blah[foo]()
}
Run Code Online (Sandbox Code Playgroud)
印刷:
foo nil: false
foo
Run Code Online (Sandbox Code Playgroud)
游乐场: https: //go.dev/play/p/src2sDSwe5H
如果您可以blah使用指针类型进行实例化,或者更好地向其传递参数,则可以删除所有中间技巧:
type FooBar interface {
*foo | *bar
stringer
}
func blah[T FooBar](t T) {
do(t)
}
func main() {
blah(&foo{})
}
Run Code Online (Sandbox Code Playgroud)