英文:
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*T
where the type checker wants to seeT
.- Calling
*new(T)
, compiles but will result in the same runtime panic becausenew(T)
returns**Count
in this case, where the pointer toCount
is stillnil
. - 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:
- 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论