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{})
}
- 1 回答
- 0 关注
- 135 浏览
添加回答
举报