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 conditionreflect.TypeOf(v).Kind() == reflect.Ptr
is evaluated at runtime, whereas instantiation ofmakeNewA
happens at compile-time. At compile-timeT
is simply constrained byany
andany
(=interface{}
) doesn't implementPtr[U]
- you can't capture information about both the pointer type and the base type with only the argument
v
. For examplemakeNew[T Ptr[U], U any](v T)
won't compile when called withmakeNew(Foo{})
andmakeNew[T Ptr[U], U any](v U)
will inferT
as**Foo
when called with*Foo