不能使用 *T 类型的变量作为参数的类型

我正在学习 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并将该指针传递给一个需要特定方法签名的函数?


MMTTMM
浏览 105回答 1
1回答

偶然的你

在您的代码中,约束FooBar和stringer. 此外,这些方法是在指针接收器上实现的。对你设计的程序的一个快速而肮脏的修复是简单地断言它*T确实是一个stringer:func blah[T FooBar]() {    t := new(T)    do(any(t).(stringer))}游乐场: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))}那么这是怎么回事?首先,类型参数与其约束之间的关系不是同一性: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}但这还不够,因为现在您的类型都没有实际实现它。两者都在指针接收器上声明了方法。第 2 步:将联合中的类型更改为指针:type FooBar interface {    *foo | *bar    stringer}此约束现在有效,但您还有另一个问题。当约束没有核心类型时,您不能声明复合文字。所以t := T{}也是无效的。我们将其更改为:func blah[T FooBar]() {    var t T // already pointer type    do(t)}现在可以编译了,但t实际上是指针类型的零值,所以它是nil. 您的程序不会崩溃,因为这些方法只返回一些字符串文字。如果您还需要初始化指针引用的内存,则blah需要了解基本类型。第 3 步:因此,您添加T foo | baras one type 参数,并将签名更改为:func blah[T foo | bar, U FooBar]() {    var t T    do(U(&t))}完毕?还没有。转换U(&t)仍然无效,因为两者的类型集U不T匹配。您现在需要FooBar在T.第 4 步:基本上,您将 的联合提取FooBar到一个类型参数中,这样在编译时它的类型集将仅包括以下两种类型之一:type FooBar[T foo | bar] interface {    *T    stringer}现在可以使用 实例化约束T foo | bar,保留类型安全、指针语义并初始化T为非零。func (f *foo) a() string {    fmt.Println("foo nil:", f == nil)    return "foo"}func main() {    blah[foo]()}印刷:foo nil: falsefoo游乐场: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{})}
打开App,查看更多内容
随时随地看视频慕课网APP