创建一个通用类型的新指针或新值的Go函数

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

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

问题

我有一个函数,它接受一个泛型类型,并应返回一个始终返回指针的函数。也就是说,如果你传递给它一个非指针类型,它应返回该类型的指针;如果你传递给它一个指针类型,它应返回相同的类型。我不想使用reflect.New,因为这是一个性能关键的应用程序。

在返回工厂函数的函数中,我不介意使用反射,但最好不要在那里使用。

这是我尝试做的事情:

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())
}
英文:

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

答案1

得分: 2

这种条件类型的问题不适合使用泛型来解决,因为当你用*Foo实例化T any时,会丢失关于基本类型的信息。事实上,你的代码仍然使用了反射和any(即interface{}),而makeN函数的返回类型将需要进行类型断言为*Foo

你目前的代码可以得到最接近的结果是:

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() // 必须使用反射
		}
	} else {
		return func() any { return new(T) } // v 不是指针,使用 new 进行分配
	}
}

然后,两个makeN函数都将返回一个包装非空*Foo值的any

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

进一步考虑:

  • 在你的第一个尝试中,return makeNewA[T]是不起作用的,因为条件reflect.TypeOf(v).Kind() == reflect.Ptr是在运行时评估的,而makeNewA的实例化是在编译时发生的。在编译时,T只是受到any的约束,而any(即interface{})并不实现Ptr[U]
  • 你无法仅通过参数v捕获关于指针类型和基本类型的信息。例如,当调用makeNew(Foo{})时,makeNew[T Ptr[U], U any](v T)将无法编译,而调用makeNew[T Ptr[U], U any](v U)时,将推断T**Foo
英文:

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

huangapple
  • 本文由 发表于 2022年3月12日 02:51:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/71443373.html
匿名

发表评论

匿名网友

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

确定