Home  >  Article  >  Backend Development  >  How to instantiate a non-zero pointer to a type parameter using a generic Go?

How to instantiate a non-zero pointer to a type parameter using a generic Go?

WBOY
WBOYforward
2024-02-11 17:36:11394browse

如何使用泛型 Go 实例化类型参数的非零指针?

php editor Xigua will introduce to you how to use non-zero pointers of generic instantiation type parameters in the Go language. In Go language, generics are a powerful feature that can increase the flexibility and reusability of code. When we need to instantiate a non-zero pointer in a generic function or method, we can use type assertions and reflection to achieve this. By using these techniques, we can create a non-zero pointer instance at runtime based on the specific type of the type parameter, thereby achieving the flexibility and versatility of generics. Let's take a closer look at the specific implementation method.

Question content

Now that type parameters are available on golang/go:master, I decided to give it a try. It seems I've hit a limitation that I can't find in the type parameters proposal. (Or I must have missed it).

I want to write a function that returns a slice of generic type values ​​with interface type constraints. If the type passed is an implementation with a pointer receiver, how do 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 the above SetGetterSlice() function is called using the *Count type as T, this code will call Set(v)## Confusion occurs when #. (Go2go Playground) Not surprising since basically the code creates 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())
    }
}

Variation of the same question

This idea doesn't work, I can't seem to find any easy way to instantiate the value pointed to.

  1. out[i] = new(T) will cause compilation to fail because it returns *T, where the type checker expects to see T.
  2. Calling
  3. *new(T) compiles but causes the same runtime panic because new(T) returns **Count here In this case, the pointer to Count is still nil.
  4. Changing the return type to a pointer fragment to
  5. T will cause compilation to fail:
  6. 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
    }
Solution

The only solution I've found so far is to require that the constructor be passed to a generic function. But it feels wrong and a bit boring. If

func F(T interface{})() []T is perfectly valid syntax, why is this needed?

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?
  1. Is this a limitation of generics in Go? Is this as good as it gets?
  2. Is this limitation known or should I raise an issue in the Go project?
Workaround

Basically you have to add a type parameter to the constraint to make

T convertible to its pointer type. In its most basic form, the technique looks like this (with anonymous constraints):

func Foo[T any, PT interface { *T; M() }]() {
    p := PT(new(T))
    p.M() // calling method on non-nil pointer
}

Playground:

https://www.php.cn/link/24aef8cb3281a2422a59b51659f1ad2e

Step-by-step solution

Your constraints

SetGetter already declare the type parameter V, so we modify the above example slightly:

// 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 define the

SetGetterSlice function, whose type parameter is T any, whose purpose is only to instantiate the constraint SetGetter.

You can then cast the expression

&out[i] to a 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:

CFE57E536C89530D9A8C38E10967A10D

This becomes more verbose because

SetGetterSlice now requires three type parameters: the original V plus T (the type with the pointer receiver) and PT (new constraints). However, when you call the function, you can omit the third - via type inference, the type parameters V and ## required to instantiate PT SetGetter[V,T] #T are all known: <pre class="brush:php;toolbar:false;">SetGetterSlice[int, Count](ints)</pre> Playground:

https://www.php.cn/link/6b061fc28f7473418a006dfa832708b1

The above is the detailed content of How to instantiate a non-zero pointer to a type parameter using a generic Go?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete