Golang中的通用类型转换

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

Generic type conversion in Golang

问题

我厌倦了手动编写像[]int32 -> []int64这样的切片转换,因为在许多情况下,您需要使用另一种类型的切片。所以我尝试编写一个通用函数来解决这个问题:

func convertSlice[T1 any, T2 any](t1 []T1) []T2 {
    t2 := make([]T2, len(t1))
    for i := range t1 {
        t2[i] = T2(t1[i])
    }
    return t2
}

并且想要像这样使用它:

a := []int{1, 2, 3, 4, 5}
var b []int64 = convertSlice[int, int64](a)

但是我无法编译它,编译器报错:./prog.go:8:14: cannot convert t1[i] (variable of type T1 constrained by any) to type T2

那么,我该如何修复这个问题呢?

实时示例:https://go.dev/play/p/YYOLFjYt4mq

当然,我可以为每种原始类型编写单独的函数,例如:

func convertNumericSlice[T1, T2 constraints.Integer | constraints.Float](t1 []T1) []T2 {}
func convertStringSlice[T1, T2 ~string](t1 []T1) []T2 {}

但这看起来并不像一个很酷的通用方法。

英文:

I got tired to write manual slice conversions like []int32 -> []int64, because there are many situations when you need to do use slice with another type.
So I tried to write a generic function for that:

func convertSlice[T1 any, T2 any](t1 []T1) []T2 {
	t2 := make([]T2, len(t1))
	for i := range t1 {
		t2[i] = T2(t1[i])
	}
	return t2
}

and want to use it like

a := []int{1, 2, 3, 4, 5}
var b []int64 = convertSlice[int, int64](a)

But I cant compile it, compiler says that ./prog.go:8:14: cannot convert t1[i] (variable of type T1 constrained by any) to type T2

So, how can I fix that?

Live example: https://go.dev/play/p/YYOLFjYt4mq

Of course, I can write separate functions for every primitive type like:

func convertNumericSlice[T1, T2 constraints.Integer | constraints.Float](t1 []T1) []T2 {}
func convertStringSlice[T1, T2 ~string](t1 []T1) []T2 {}

but that doesnt look like cool generic way at all.

答案1

得分: 1

如果您有一个包含所有数字类型的类型,那么可以在它们之间进行转换。

请尝试以下代码:

type Number interface {
	int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

func convertSlice[T1 Number, T2 Number](t1 []T1) []T2 {
	t2 := make([]T2, len(t1))
	for i := range t1 {
		t2[i] = T2(t1[i])
	}
	return t2
}

Go Playground演示

英文:

If you have a type with all number types then it's possible to convert in between.

Try like below:

type Number interface {
	int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

func convertSlice[T1 Number, T2 Number](t1 []T1) []T2 {
	t2 := make([]T2, len(t1))
	for i := range t1 {
		t2[i] = T2(t1[i])
	}
	return t2
}

Go play ground demo

答案2

得分: 1

无法将具有任意约束的类型参数进行转换。规范中提到了以下内容(转换):

[...] 如果满足以下条件之一,x 也可以转换为类型 T:

  • V 和 T 都是类型参数,并且 V 的类型集中的每种类型都可以转换为 T 的类型集中的每种类型。

如果 T1T2 都受到 any 的约束,那么两个类型集合实际上包含任何可能的类型。

尽管你的 convertSlice 函数的特定实例可能是有效的,但编译器无法静态地证明转换 T2(t1) 总是有效的。理论上,你可以实例化 convertSlice[string, chan func()],而字符串显然无法转换为函数通道。

编写一个通用函数的唯一方法是使用反射和 CanConvert 方法,但你需要决定如何处理 CanConvert 返回 false 的情况。抛出异常?返回零值?

如果选择使用泛型,那么你已经在选择类型安全性方面做出了选择,因此引入反射似乎是违背直觉的。使用泛型,你必须为每组可转换的基础类型编写函数。主要的特殊情况包括:

数字:

type Number interface {
    constraints.Integer | constraints.Float
}

convertNumbers[T1, T2 Number](t1 []T1) []T2 {}

字符串 - 它对于字节切片和符文切片进行了特殊处理,但字节切片和符文切片之间不能相互转换,因此你需要在这里编写两个函数:

convertStrings[T1, T2 ~string | ~[]byte](t1 []T1) []T2 {}
// 或者
convertStrings[T1, T2 ~string | ~[]rune](t1 []T1) []T2 {}

复数:

convertComplex[T1, T2 ~complex64 | ~complex128](t1 []T1) []T2 {}
英文:

It is not possible to convert type parameters with arbitrary constraints. The spec mentions that (conversions):

> [...] x can also be converted to type T if one of the following conditions applies:
>
> - Both V and T are type parameters and a value of each type in V's type set can be converted to each type in T's type set.

If both T1 and T2 are constrained with any, both type sets are virtually include any possible type.

Even though a particular instantiation of your convertSlice function may be valid, the compiler can't statically prove that the conversion T2(t1) is always valid. In theory you could instantiate convertSlice[string, chan func()] and then a string obviously can't be converted to a channel of functions.

The only way to write a catch-all function is to use reflection with the CanConvert method, but you would be left with deciding how to handle the case where CanConvert returns false. Panic? Return a zero value?

If you choose to use generics, you are already making a choice towards type safety, so introducing reflection seems counter-intuitive. With generics, you do have to write functions for each set of convertible underlying types. The main special cases are:

Numbers:

type Number interface {
    constraints.Integer | constraints.Float
}

convertNumbers[T1, T2 Number](t1 []T1) []T2 {}

Strings — it is special-cased for byte slices and rune slices, but byte slices and rune slices aren't convertible to each other, so you'd need two functions here):

convertStrings[T1, T2 ~string | ~[]byte](t1 []T1) []T2 {}
// or
convertStrings[T1, T2 ~string | ~[]rune](t1 []T1) []T2 {}

Complex:

convertComplex[T1, T2 ~complex64 | ~complex128](t1 []T1) []T2 {}

huangapple
  • 本文由 发表于 2023年2月10日 03:07:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/75403318.html
匿名

发表评论

匿名网友

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

确定