Home > Enterprise >  Go function to create either new pointer or new value of a generic type
Go function to create either new pointer or new value of a generic type

Time:03-13

I have a function which takes a generic type and should return a function that always return a pointer. I.e. if you pass it a non-pointer type it should return a pointer to that type, if you pass it a pointer type it should return the same type. I don't want to use reflect.New as it's a performance-critical app.

I don't mind using reflection in the function that returns the factory function, however ideally not even there.

This is what I'm trying to do:

package main

import (
    "fmt"
    "reflect"
)

type Ptr[T any] interface {
    *T
}

func makeNewA[T Ptr[U], U any]() any {
    return new(U)
}

func makeNewB[T any]() any {
    return new(T)
}

func makeNew[T any](v T) func() any {
    if reflect.TypeOf(v).Kind() == reflect.Ptr {
        return makeNewA[T] // <-- error: T does not match *U
    } else {
        return makeNewB[T]
    }
}

type Foo struct{}

func main() {
    make1 := makeNew(Foo{})
    make2 := makeNew(&Foo{})

    // should both return &Foo{}
    fmt.Println(make1())
    fmt.Println(make2())
}

CodePudding user response:

This kind of conditional typing isn't nicely solved with generics, because when you instantiate T any with *Foo you lose information about the base type. As matter of fact your code still uses reflection and any (= interface{}), and the return type of the makeN functions will have to be type-asserted to *Foo.

The closest you can get with your current code is:

func makeNew[T any](v T) func() any {
    if typ := reflect.TypeOf(v); typ.Kind() == reflect.Ptr {
        elem := typ.Elem()
        return func() any {
            return reflect.New(elem).Interface() // must use reflect
        }
    } else {
        return func() any { return new(T) } // v is not ptr, alloc with new
    }
}

Then both maker functions will return an any that wraps a non-nil *Foo value:

fmt.Printf("%T, %v\n", make1(), make1()) // *main.Foo, &{}
fmt.Printf("%T, %v\n", make2(), make2()) // *main.Foo, &{}

Playground: https://gotipplay.golang.org/p/kVUM-qVLLHG

Further considerations:

  • return makeNewA[T] in your first attempt does not work because the condition reflect.TypeOf(v).Kind() == reflect.Ptr is evaluated at runtime, whereas instantiation of makeNewA happens at compile-time. At compile-time T is simply constrained by any and any (= interface{}) doesn't implement Ptr[U]
  • you can't capture information about both the pointer type and the base type with only the argument v. For example makeNew[T Ptr[U], U any](v T) won't compile when called with makeNew(Foo{}) and makeNew[T Ptr[U], U any](v U) will infer T as **Foo when called with *Foo
  • Related