英文:
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()
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论