你好!要实例化一个非nil指针,可以使用Go语言的泛型参数。

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

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

同样问题的变体

以下这些想法都行不通,我似乎找不到任何简单的方法来实例化指向的值。

  1. out[i] = new(T)将导致编译失败,因为它返回一个*T,而类型检查器希望看到的是T
  2. 调用*new(T)可以编译通过,但会导致相同的运行时恐慌,因为在这种情况下new(T)返回**Count,其中指向Count的指针仍然是nil
  3. 将返回类型更改为指向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) })
}

总结

我的问题按优先级排序如下:

  1. 我是否忽略了一些明显的东西?
  2. 这是Go中泛型的限制,这就是最好的解决办法吗?
  3. 这个限制是否已知,还是我应该在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.

  1. out[i] = new(T) will result in a compile failure, as it returns a *T where the type checker wants to see T.
  2. Calling *new(T), compiles but will result in the same runtime panic because new(T) returns **Count in this case, where the pointer to Count is still nil.
  3. Changing the return type to a slice of pointer to T will 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:

  1. Am I overlooking something obvious?
  2. Is this a limitation of generics in Go and this is as good as it gets?
  3. 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]所需的类型参数VT

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 &amp;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
        // &amp;out[i] has type *T
        // PT constraint includes *T
		p := PT(&amp;out[i]) // valid conversion!
		p.Set(v)         // calling with non-nil pointer receiver
	}

	return out
}

Full program:

package main

import (
	&quot;fmt&quot;
)

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(&amp;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 (
	&quot;fmt&quot;
)

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(&amp;obj)
	pointer.Scan() // calling method on non-nil pointer

	return &amp;obj
}

func main() {
	user := setRemovedState[User](true)
	account := setRemovedState[Account](true)

	fmt.Printf(&quot;User: %v\n&quot;, *user)
	fmt.Printf(&quot;Account: %v\n&quot;, *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 (
&quot;fmt&quot;
)
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 &amp;out
}
func main() {
ints := []int{1, 2, 3, 4, 5}
sgs := mapp(ints, FromInt)
fmt.Printf(&quot;%T\n&quot;,sgs)
for _, s := range sgs {
fmt.Println(s.Get())
}
fmt.Println()
sgs = mappp(ints, FromInt)
fmt.Printf(&quot;%T\n&quot;,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. 你好!要实例化一个非nil指针,可以使用Go语言的泛型参数。

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

huangapple
  • 本文由 发表于 2021年10月14日 23:09:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/69573113.html
匿名

发表评论

匿名网友

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

确定