Golang同时使用接口和实现的泛型

huangapple go评论88阅读模式
英文:

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

当你将XY都约束为any时,你失去了所有的接口实现关系。在编译时唯一知道的是XY是不同的类型,你不能在函数体内将一个赋值给另一个。

一种使其编译通过的方法是使用显式断言:

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()
	}
}

huangapple
  • 本文由 发表于 2022年3月22日 13:36:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/71567357.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定