为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
MMTTMM 2022-11-28 14:42:29
我正在学习 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 mainimport "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并将该指针传递给一个需要特定方法签名的函数?
查看完整描述

1 回答

?
偶然的你

TA贡献1841条经验 获得超3个赞

在您的代码中,约束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: false

foo

游乐场: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{})

}


查看完整回答
反对 回复 2022-11-28
  • 1 回答
  • 0 关注
  • 101 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信