英文:
Golang generics with interface and implementation at same time
问题
我正在尝试编写以下函数:
func Fill[X any](slice []*X){
for i := range slice {
slice[i] = new(X)
}
}
xs := make([]*int, 10) // 用nil填充
Fill(xs) // 现在用new(int)填充
这个函数可以正常工作,但是...如果我想使用一个接口的切片并提供一个具体类型怎么办?
func Fill[X, Y any](slice []X){
for i := range slice {
slice[i] = new(Y) // 不起作用!
}
}
xs := make([]sync.Locker, 10) // 用nil填充
Fill[sync.Locker,sync.Mutex](xs) // ouch
我尝试了一些组合但没有成功,有没有办法或者go1.18不支持这样的关系?
英文:
I am trying to write the following function:
func Fill[X any](slice []*X){
for i := range slice {
slice[i] = new(X)
}
}
xs := make([]*int, 10) // fill with nils
Fill(xs) // now fill with new(int)
That works fine but… if I want to use a slice of interfaces and provide a concrete type?
func Fill[X, Y any](slice []X){
for i := range slice {
slice[i] = new(Y) // not work!
}
}
xs := make([]sync.Locker, 10) // fill with nils
Fill[sync.Locker,sync.Mutex](xs) // ouch
I try some combinations without success, is there a way or go1.18 does not support such relations?
答案1
得分: 4
当你将X和Y都约束为any时,你失去了所有的接口实现关系。在编译时唯一知道的是X和Y是不同的类型,你不能在函数体内将一个赋值给另一个。
一种使其编译通过的方法是使用显式断言:
func Fill[X, Y any](slice []X) {
for i := range slice {
slice[i] = any(*new(Y)).(X)
}
}
但是这会导致恐慌,如果Y实际上没有实现X,就像你的情况一样,因为它是实现sync.Locker的*sync.Mutex(指针类型)。
此外,当Y实例化为指针类型时,你失去了关于基本类型的信息,因此零值,包括*new(Y),将是nil,所以你实际上并没有比make(只是有类型的nil vs. nil接口)有任何基准改进。
你想要做的是将Y约束为X,就像Fill[X any, Y X](slice []X),但这是不可能的,因为1)类型参数不能用作约束;和/或2)约束不能直接嵌入类型参数。它也会像上面那样初始化nil。
一个更好的解决方案是使用构造函数而不是第二个类型参数:
func main() {
xs := make([]sync.Locker, 10)
Fill(xs, func() sync.Locker { return &sync.Mutex{} })
}
func Fill[X any](slice []X, f func() X) {
for i := range slice {
slice[i] = f()
}
}
英文:
When you constrain both X and Y to any, you lose all interface-implementor relationship. The only thing that is known at compile time is that X and Y are different types, and you can't assign one to the another within the function body.
A way to make it compile is to use an explicit assertion:
func Fill[X, Y any](slice []X) {
for i := range slice {
slice[i] = any(*new(Y)).(X)
}
}
But this panics if Y doesn't really implement X, as in your case, since it is *sync.Mutex (pointer type) that implements sync.Locker.
Moreover, when Y is instantiated with a pointer type, you lose information about the base type, and therefore the zero value, including *new(Y) would be nil, so you don't really have a baseline improvement over make (just typed nils vs. nil interfaces).
What you would like to do is to constrain Y to X, like Fill[X any, Y X](slice []X) but this is not possible because 1) a type parameter can't be used as a constraint; and/or 2) a constraint can't embed a type parameter directly. It also initializes nils as the above.
A better solution is to use a constructor function instead of a second type parameter:
func main() {
xs := make([]sync.Locker, 10)
Fill(xs, func() sync.Locker { return &sync.Mutex{} })
}
func Fill[X any](slice []X, f func() X) {
for i := range slice {
slice[i] = f()
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论