在Go语言中,可以使用函数指针来处理多种类型的情况。

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

Using a Pointer to a Function for multiple types in GoLang

问题

我有很多(20+)以下形式的“枚举类型”。我想做的是在一个结构体中按名称列出所有的枚举类型,并且有一个指向返回所有类型的函数的指针。下面是一个简化的示例。

package main

type Fruit int

const (
	unknownFruit Fruit = iota // 必须是第一个
	Apple
	Banana
	fruitDone
)

func FruitTypes() []Fruit {
	var res []Fruit
	for typ := unknownFruit + 1; typ < fruitDone; typ++ {
		res = append(res, typ)
	}
	return res
}

type Car int

const (
	unknownCar Car = iota // 必须是第一个
	BMW
	Mercedes
	doneCar
)

func CarTypes() []Car {
	var res []Car
	for typ := unknownCar + 1; typ < doneCar; typ++ {
		res = append(res, typ)
	}
	return res
}


func main() {
	enumTypes := []struct {
		Name        string
		Length      int
		ConvertFunc func(int) string
	}{
		{Name: "Fruit", Length: int(fruitDone), ConvertFunc: ConvertEnum[Fruit]},
		{Name: "Car", Length: int(doneCar), ConvertFunc: ConvertEnum[Car]},
	}

	for _, enumType := range enumTypes {
		fmt.Println(enumType.Name)
		for i := 0; i < enumType.Length; i++ {
			fmt.Printf(" -- %s", enumType.ConvertFunc(i))
		}
	}
}

func ConvertEnum[T any](raw int) string {
	return fmt.Sprintf("%v", T(raw)) // #2 - This will not convert
}

不幸的是,我似乎无法正确声明函数指针。正确的返回类型是什么?

(另外,是否有一种方法可以为这些枚举类型声明一个接口和/或使用泛型,以便我不必拥有FruitType()CarType()PetType()等等?

=================

更新 - 感谢@Woody1193,我觉得我离成功更近了。我现在使用了枚举是连续数字的事实,所以我实际上不需要传入除了类型之外的任何细节,这样我就可以在循环内部将其转换回来(参见上面的标记#1)。

然而,在上面的代码中,我得到了以下错误:

./main.go:55:64: cannot use EnumType[Fruit] (value of type func(done Fruit) []Fruit) as type func() []int in struct literal
./main.go:56:60: cannot use EnumType[Car] (value of type func(done Car) []Car) as type func() []int in struct literal
./main.go:62:43: too many arguments in call to typ.TypeFunction

=================

更新2 - 我尝试采用不同的角度,只传入转换函数,以将其从结构体中移出(参见上面的代码)。但现在它不允许我进行类型转换 在Go语言中,可以使用函数指针来处理多种类型的情况。

./main.go:68:31: cannot convert raw (variable of type int) to type T
英文:

I have a lot (20+) "Enum Types" of the following form. What I'd like to do is list all the Enum types by name in a struct (see below) and a pointer to a function that returns all of the types. See below for an abbreviated sample.

package main

type Fruit int

const (
	unknownFruit Fruit = iota // must be first
	Apple
	Banana
	fruitDone
)

func FruitTypes() []Fruit {
	var res []Fruit
	for typ := unknownFruit + 1; typ &lt; fruitDone; typ++ {
		res = append(res, typ)
	}
	return res
}

type Car int

const (
	unknownCar Car = iota // must be first
	BMW
	Mercedes
	doneCar
)

func CarTypes() []Car {
	var res []Car
	for typ := unknownCar + 1; typ &lt; doneCar; typ++ {
		res = append(res, typ)
	}
	return res
}


func main() {
	enumTypes := []struct {
		Name        string
		Length      int
		ConvertFunc func(int) string
	}{
		{Name: &quot;Fruit&quot;, Length: int(doneFruit), ConvertFunc: ConvertEnum[Fruit]},
		{Name: &quot;Car&quot;, Length: int(doneCar), ConvertFunc: ConvertEnum[Car]},
	}

	for _, enumType := range enumTypes {
		fmt.Println(enumType.Name)
		for i := 0; i &lt; enumType.Length; i++ {
			fmt.Printf(&quot; -- %s&quot;, enumType.ConvertFunc(i))
		}
	}
}

func ConvertEnum[T any](raw int) string {
	return fmt.Sprintf(&quot;%v&quot;, T(raw)) // #2 - This will not convert
}

Unfortunately, I cannot seem to declare the function pointer properly. What's the correct return type?

(As an aside, is there a way to declare an interface for these enum types and/or use generics so that I don't have to have FruitType(), CarType(), PetType()...

=================

UPDATE - thanks to @Woody1193, I feel like I'm much closer. I'm now using the fact that enums are sequential numbers, so I don't really need to pass in any details other than the type so I can cast it back inside the loop (see marker #1 above).

However, in the above, I get:

./main.go:55:64: cannot use EnumType[Fruit] (value of type func(done Fruit) []Fruit) as type func() []int in struct literal
./main.go:56:60: cannot use EnumType[Car] (value of type func(done Car) []Car) as type func() []int in struct literal
./main.go:62:43: too many arguments in call to typ.TypeFunction

=================

Update #2 - I've tried to take a different angle, and just pass in the conversion function, to get it out of the struct. (See above). But now it won't let me cast it 在Go语言中,可以使用函数指针来处理多种类型的情况。

./main.go:68:31: cannot convert raw (variable of type int) to type T

答案1

得分: 3

你在这里遇到的问题是CarFruit都不是整数,所以TypesFunction不会接受CarTypesFruitTypes。在这种情况下,泛型是不起作用的,因为声明这样的对象切片需要它们都具有相同的类型参数,这意味着你的代码仍然无法工作。

因此,你最好的选择是修改CarTypesFruitTypes,使它们具有相同的签名func() []int

func FruitTypes() []int {
    var res []int
    for typ := unknownFruit + 1; typ < fruitDone; typ++ {
        res = append(res, typ)
    }
    return res
}

func CarTypes() []int {
    var res []int
    for typ := unknownCar + 1; typ < doneCar; typ++ {
        res = append(res, typ)
    }
    return res
}

当然,这意味着当你想要特定类型的枚举时,你将需要使用类型转换,但代码将能够工作。

另外,你可以使用泛型来生成函数本身:

func EnumType[T ~int](done T) []int {
    var res []int
    for typ := T(1); typ < done; typ++ {
        res = append(res, typ)
    }

    return res
}

func ConvertEnum[T ~int](raw ...int) []T {
    res := make([]T, len(raw))
    for i, typ := range raw {
        res[i] = T(typ)
    }

    return res
}

enumTypes := []struct {
    Name          string
    TypesFunction func() []int
}{
    {Name: "Fruit", TypesFunction: EnumType[Fruit]},
    {Name: "Car", TypesFunction: EnumType[Car]},
}
英文:

The issue you're having here is that neither Car nor Fruit is actually an integer, so TypesFunction will not accept CarTypes or FruitTypes. Generics will not work in this situation because declaring a slice of such objects would require them to all have the same type parameter, which means your code still wouldn't work.

Therefore, your best option is to modify CarTypes and FruitTypes so that they have the same signature, func() []int:

func FruitTypes() []int {
var res []int
for typ := unknownFruit + 1; typ &lt; fruitDone; typ++ {
res = append(res, typ)
}
return res
}
func CarTypes() []int {
var res []int
for typ := unknownCar + 1; typ &lt; doneCar; typ++ {
res = append(res, typ)
}
return res
}

Of course, this will mean that you'll have to use type casting when you want the specific type of enum, but the code will work.

As an aside, you can use generics to generate the function itself:

func EnumType[T ~int](done T) []int {
var res []int
for typ := T(1); typ &lt; done; typ++ {
res = append(res, typ)
}
return res
}
func ConvertEnum[T ~int](raw ...int) []T {
res := make([]T, len(raw))
for i, typ := range raw {
res[i] = T(typ)
}
return res
}
enumTypes := []struct {
Name          string
TypesFunction func() []int
}{
{Name: &quot;Fruit&quot;, TypesFunction: EnumType[Fruit]},
{Name: &quot;Car&quot;, TypesFunction: EnumType[Car]},
}

huangapple
  • 本文由 发表于 2022年9月29日 06:46:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/73888563.html
匿名

发表评论

匿名网友

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

确定