英文:
How can I instantiate a non-nil pointer of type argument with generic Go?
问题
现在golang/go:master上已经支持类型参数,我决定试一试。似乎我遇到了一个在类型参数提案中找不到的限制(或者我可能错过了)。
我想编写一个函数,返回一个具有接口类型约束的通用类型的值切片。如果传递的类型是具有指针接收器的实现,我们该如何实例化它?
type SetGetter[V any] interface {
	Set(V)
	Get() V
}
// SetGetterSlice将类型为V的切片转换为类型为T的切片,
// 对于values中的每个条目,调用T.Set()。
func SetGetterSlice[V any, T SetGetter[V]](values []V) []T {
	out := make([]T, len(values))
	for i, v := range values {
		out[i].Set(v) // 如果T具有指针接收器,则会发生恐慌!
	}
	return out
}
当使用*Count类型作为T调用上述的SetGetterSlice()函数时,此代码将在调用Set(v)时发生恐慌。(Go2go playground)这并不奇怪,因为基本上代码创建了一个包含nil指针的切片:
// Count实现SetGetter接口
type Count struct {
	x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int  { return c.x }
func main() {
	ints := []int{1, 2, 3, 4, 5}
	sgs := SetGetterSlice[int, *Count](ints)
	
	for _, s := range sgs {
		fmt.Println(s.Get())
	}
}
同样问题的变体
以下这些想法都行不通,我似乎找不到任何简单的方法来实例化指向的值。
out[i] = new(T)将导致编译失败,因为它返回一个*T,而类型检查器希望看到的是T。- 调用
*new(T)可以编译通过,但会导致相同的运行时恐慌,因为在这种情况下new(T)返回**Count,其中指向Count的指针仍然是nil。 - 将返回类型更改为指向
T的切片将导致编译失败: 
func SetGetterSlice[V any, T SetGetter[V]](values []V) []*T {
	out := make([]*T, len(values))
	for i, v := range values {
		out[i] = new(T)
		out[i].Set(v) // 如果T具有指针接收器,则会发生恐慌
	}
	return out
}
func main() {
	ints := []int{1, 2, 3, 4, 5}
	SetGetterSlice[int, Count](ints)
    // Count不满足SetGetter[V]:方法签名错误
}
解决方法
到目前为止,我找到的唯一解决方法是要求将一个构造函数传递给通用函数。但这感觉不对,而且有点繁琐。如果func F(T interface{})() []T是完全有效的语法,为什么需要这样做呢?
func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T {
	out := make([]T, len(values))
	for i, v := range values {
		out[i] = constructor()
		out[i].Set(v)
	}
	return out
}
// ...
func main() {
	ints := []int{1, 2, 3, 4, 5}
	SetGetterSlice[int, *Count](ints, func() *Count { return new(Count) })
}
总结
我的问题按优先级排序如下:
- 我是否忽略了一些明显的东西?
 - 这是Go中泛型的限制,这就是最好的解决办法吗?
 - 这个限制是否已知,还是我应该在Go项目中提出问题?
 
英文:
Now that type parameters are available on golang/go:master, I decided to give it a try. It seems that I'm running into a limitation I could not find in the Type Parameters Proposal. (Or I must have missed it).
I want to write a function which returns a slice of values of a generic type with the constraint of an interface type. If the passed type is an implementation with a pointer receiver, how can we instantiate it?
type SetGetter[V any] interface {
	Set(V)
	Get() V
}
// SetGetterSlice turns a slice of type V into a slice of type T,
// with T.Set() called for each entry in values.
func SetGetterSlice[V any, T SetGetter[V]](values []V) []T {
	out := make([]T, len(values))
	for i, v := range values {
		out[i].Set(v) // panic if T has pointer receiver!
	}
	return out
}
When calling the above SetGetterSlice() function with the *Count type as T, this code will panic upon calling Set(v). (Go2go playground) To no surprise, as basically the code created a slice of nil pointers:
// Count implements SetGetter interface
type Count struct {
	x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int  { return c.x }
func main() {
	ints := []int{1, 2, 3, 4, 5}
	sgs := SetGetterSlice[int, *Count](ints)
	
	for _, s := range sgs {
		fmt.Println(s.Get())
	}
}
Variations of the same problem
This ideas won't work, and I can't seem to find any simple way to instantiate the pointed value.
out[i] = new(T)will result in a compile failure, as it returns a*Twhere the type checker wants to seeT.- Calling 
*new(T), compiles but will result in the same runtime panic becausenew(T)returns**Countin this case, where the pointer toCountis stillnil. - Changing the return type to a slice of pointer to 
Twill result in a compile failure: 
func SetGetterSlice[V any, T SetGetter[V]](values []V) []*T {
	out := make([]*T, len(values))
	for i, v := range values {
		out[i] = new(T)
		out[i].Set(v) // panic if T has pointer receiver
	}
	return out
}
func main() {
	ints := []int{1, 2, 3, 4, 5}
	SetGetterSlice[int, Count](ints)
    // Count does not satisfy SetGetter[V]: wrong method signature
}
Workaround
The only solution I found until now, is to require a constructor function to be passed to the generic function. But this just feels wrong and a bit tedious. Why would this be required if func F(T interface{})() []T is perfectly valid syntax?
func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T {
	out := make([]T, len(values))
	for i, v := range values {
		out[i] = constructor()
		out[i].Set(v)
	}
	return out
}
// ...
func main() {
	ints := []int{1, 2, 3, 4, 5}
	SetGetterSlice[int, *Count](ints, func() *Count { return new(Count) })
}
Summary
My questions, in order of priority:
- Am I overlooking something obvious?
 - Is this a limitation of generics in Go and this is as good as it gets?
 - Is this limitation known or should I raise an issue at the Go project?
 
答案1
得分: 20
基本上,你需要在约束条件中添加一个额外的类型参数,使得T可以转换为其指针类型。这个技术的基本形式如下所示(使用匿名约束):
func Foo[T any, PT interface { *T; M() }]() {
    p := PT(new(T))
    p.M() // 在非nil指针上调用方法
}
Playground: https://go.dev/play/p/L00tePwrDfx
逐步解决方案
你的约束SetGetter已经声明了一个类型参数V,所以我们稍微修改上面的示例:
// V是你的原始类型参数
// T是额外的辅助参数
type SetGetter[V any, T any] interface {
    Set(V)
    Get() V
    *T
}
然后,你使用类型参数T any定义了SetGetterSlice函数,其目的只是实例化约束SetGetter。
然后,你将能够将表达式&out[i]转换为指针类型,并成功在指针接收器上调用方法:
// T是具有指针接收器方法的类型
// PT是具有*T的SetGetter约束
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
    out := make([]T, len(values))
    for i, v := range values {
        // out[i]的类型是T
        // &out[i]的类型是*T
        // PT约束包括*T
        p := PT(&out[i]) // 有效的转换!
        p.Set(v)         // 使用非nil指针接收器调用
    }
    return out
}
完整程序:
package main
import (
    "fmt"
)
type SetGetter[V any, T any] interface {
    Set(V)
    Get() V
    *T
}
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
    out := make([]T, len(values))
    for i, v := range values {
        p := PT(&out[i])
        p.Set(v)
    }
    return out
}
// Count实现了SetGetter接口
type Count struct {
    x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int  { return c.x }
func main() {
    ints := []int{1, 2, 3, 4, 5}
    // 使用基本类型实例化
    sgs := SetGetterSlice[int, Count](ints)
    for _, s := range sgs {
        fmt.Println(s.Get()) // 每行打印1,2,3,4,5
    }
}
这变得更冗长,因为SetGetterSlice现在需要三个类型参数:原始的V加上T(具有指针接收器的类型)和PT(新的约束)。然而,当你调用该函数时,可以省略第三个参数 - 使用类型推断,已知用于实例化PT SetGetter[V,T]所需的类型参数V和T:
SetGetterSlice[int, Count](ints)
Playground: https://go.dev/play/p/gcQZnw07Wp3
英文:
Basically you have to add one more type parameter to the constraint to make T convertible to its pointer type. In its most basic form, this technique looks like the following (with an anonymous constraint):
func Foo[T any, PT interface { *T; M() }]() {
    p := PT(new(T))
    p.M() // calling method on non-nil pointer
}
Playground: https://go.dev/play/p/L00tePwrDfx
<hr>
Step by step solution
Your constraint SetGetter already declares a type param V, so we slightly modify the example above:
// V is your original type param
// T is the additional helper param
type SetGetter[V any, T any] interface {
	Set(V)
	Get() V
    *T
}
Then you define the SetGetterSlice function with the type parameter T any, whose purpose is just to instantiate the constraint SetGetter.
You will then be able to convert the expression &out[i] to the pointer type, and successfully call the method on the pointer receiver:
// T is the type with methods with pointer receiver
// PT is the SetGetter constraint with *T
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
	out := make([]T, len(values))
	for i, v := range values {
        // out[i] has type T
        // &out[i] has type *T
        // PT constraint includes *T
		p := PT(&out[i]) // valid conversion!
		p.Set(v)         // calling with non-nil pointer receiver
	}
	return out
}
Full program:
package main
import (
	"fmt"
)
type SetGetter[V any, T any] interface {
	Set(V)
	Get() V
    *T
}
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
	out := make([]T, len(values))
	for i, v := range values {
		p := PT(&out[i])
		p.Set(v)
	}
	return out
}
// Count implements SetGetter interface
type Count struct {
	x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int  { return c.x }
func main() {
	ints := []int{1, 2, 3, 4, 5}
    // instantiate with base type
	sgs := SetGetterSlice[int, Count](ints)
	for _, s := range sgs {
		fmt.Println(s.Get()) // prints 1,2,3,4,5 each in a newline
	}
}
This becomes more verbose because SetGetterSlice now requires three type parameters: the original V plus T (the type with pointer receivers) and PT (the new constraint). However when you call the function, you can omit the third one – with type inference, both type params V and T required to instantiate PT SetGetter[V,T] are already known:
SetGetterSlice[int, Count](ints)
Playground: https://go.dev/play/p/gcQZnw07Wp3
答案2
得分: 2
花了几个小时来理解它。
所以决定添加我的示例。
package main
import (
	"fmt"
)
type User struct {
	FullName string
	Removed  bool
}
type Account struct {
	Name    string
	Removed bool
}
type Scanner[T User | Account] interface {
	Scan()
	*T
}
type Model interface {
	User | Account
}
func (user *User) Scan() {
	user.FullName = `在扫描方法中更改`
	user.Removed = true
}
func (account *Account) Scan() {
	account.Name = `在扫描方法中更改`
	account.Removed = true
}
func setRemovedState[T Model, PT Scanner[T]](state bool) *T {
	var obj T
	pointer := PT(&obj)
	pointer.Scan() // 在非nil指针上调用方法
	return &obj
}
func main() {
	user := setRemovedState[User](true)
	account := setRemovedState[Account](true)
	fmt.Printf("User: %v\n", *user)
	fmt.Printf("Account: %v\n", *account)
}
英文:
Spent a few hours to understand it.
So decided to add my example.
package main
import (
	"fmt"
)
type User struct {
	FullName string
	Removed  bool
}
type Account struct {
	Name    string
	Removed bool
}
type Scanner[T User | Account] interface {
	Scan()
	*T
}
type Model interface {
	User | Account
}
func (user *User) Scan() {
	user.FullName = `changed in scan method`
	user.Removed = true
}
func (account *Account) Scan() {
	account.Name = `changed in scan method`
	account.Removed = true
}
func setRemovedState[T Model, PT Scanner[T]](state bool) *T {
	var obj T
	pointer := PT(&obj)
	pointer.Scan() // calling method on non-nil pointer
	return &obj
}
func main() {
	user := setRemovedState[User](true)
	account := setRemovedState[Account](true)
	fmt.Printf("User: %v\n", *user)
	fmt.Printf("Account: %v\n", *account)
}
答案3
得分: 1
你也可以尝试稍微不同的方法来解决这个问题,保持简单。
package main
import (
	"fmt"
)
func mapp[T any, V any](s []T, h func(T) V) []V {
	z := make([]V, len(s))
	for i, v := range s {
		z[i] = h(v)
	}
	return z
}
func mappp[T any, V any](s []T, h func(T) V) []V {
	z := make([]V, 0, len(s))
	for _, v := range s {
		z = append(z, h(v))
	}
	return z
}
// Count implements SetGetter interface
type Count struct {
	x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int  { return c.x }
func FromInt(x int) *Count {
	var out Count
	out.x = x
	return &out
}
func main() {
	ints := []int{1, 2, 3, 4, 5}
	sgs := mapp(ints, FromInt)
	fmt.Printf("%T\n", sgs)
	for _, s := range sgs {
		fmt.Println(s.Get())
	}
	fmt.Println()
	sgs = mappp(ints, FromInt)
	fmt.Printf("%T\n", sgs)
	for _, s := range sgs {
		fmt.Println(s.Get())
	}
}
这个代码片段类似于你的 func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T,但没有复杂的冗长性。解决这个问题对我来说也没有任何困扰。
你可以在这里查看代码运行的结果:https://go2goplay.golang.org/p/vzViKwiJJkZ
英文:
you can also try to attack the problem slighty differently, to keep it simple.
package main
import (
"fmt"
)
func mapp[T any, V any](s []T, h func(T) V) []V {
z := make([]V, len(s))
for i, v := range s {
z[i] = h(v)
}
return z
}
func mappp[T any, V any](s []T, h func(T) V) []V {
z := make([]V, 0, len(s))
for _, v := range s {
z = append(z, h(v))
}
return z
}
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int  { return c.x }
func FromInt(x int) *Count {
var out Count
out.x = x
return &out
}
func main() {
ints := []int{1, 2, 3, 4, 5}
sgs := mapp(ints, FromInt)
fmt.Printf("%T\n",sgs)
for _, s := range sgs {
fmt.Println(s.Get())
}
fmt.Println()
sgs = mappp(ints, FromInt)
fmt.Printf("%T\n",sgs)
for _, s := range sgs {
fmt.Println(s.Get())
}
}
https://go2goplay.golang.org/p/vzViKwiJJkZ
It is like your func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T but without the complex verbosity. It also gave me zero pain to solve.
答案4
得分: 0
编辑:请参考blackgreen的答案,我在浏览他们提供的相同文档时,也自己找到了这个答案。我本来打算根据那个答案来更新我的回答,但现在我不需要了。:-)
可能有更好的方法,这个方法似乎有点笨拙,但我可以通过使用reflect来解决这个问题:
if reflect.TypeOf(out[0]).Kind() == reflect.Ptr {
	x := reflect.ValueOf(out).Index(i)
	x.Set(reflect.New(reflect.TypeOf(out[0]).Elem()))
}
我只是将上面的四行代码添加到你的示例中。临时变量是从一些调试中剩下的,显然可以删除。Playground链接
英文:
Edit: see blackgreen's answer, which I also found later on my own while scanning through the same documentation they linked.  I was going to edit this answer to update based on that, but now I don't have to. ![]()
There is probably a better way—this one seems a bit clumsy—but I was able to work around this with reflect:
if reflect.TypeOf(out[0]).Kind() == reflect.Ptr {
	x := reflect.ValueOf(out).Index(i)
	x.Set(reflect.New(reflect.TypeOf(out[0]).Elem()))
}
I just added the above four lines to your example. The temporary variable is left over from some debug and obviously can be removed. Playground link
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论