Golang中为什么这个泛型函数无法正常工作?
Golang中为什么这个泛型函数无法正常工作?
A 行处的代码无法工作(使用 -G=3 编译的 go1.17.1)
错误信息是:t.Id undefined (type bound for T has no method Id)。
package main
import "reflect"
type a struct {
Id uint
}
type b struct {
Id int
}
func generic[T any](t T) {
typ := reflect.TypeOf(t)
println(typ.String())
for idx := 0 ; idx < typ.NumField() ; idx++ {
println(idx,typ.Field(idx).Name)
}
println(t.Id) // A 这一行无法工作
}
func main() {
generic(a{10})
generic(b{10})
}
更多关于Golang中为什么这个泛型函数无法正常工作?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
在使用泛型时,使用反射也没有太大意义。泛型的目的之一就是为了避免使用反射,还有其他用例。即使在使用泛型时,接口(和契约)对于定义泛型类型的行为也会有很大帮助。
更多关于Golang中为什么这个泛型函数无法正常工作?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
关键在于
func generic需要在编译时了解t的能力
根据我对泛型的理解,如果我们有这样一个泛型函数:
func generic[T any](t T) {
...
}
并且当我们用不同的类型调用它时,像这样:
generic(a{1})
generic(b{1})
编译器应该能从参数类型推断出 T,并用推断出的类型实例化一个 generic 的副本,所有这些都可以在编译时完成。这应该能正常工作。
你好 @oldoldman,
参数 t 可以是任何类型(因为 [T any]),因此无法对其真实类型做出任何假设。
为了让函数 generic 能够利用 a 和 b 的具体类型和行为,你可以使用结构体方法和类型约束:
type a struct {
id uint
}
func (a a) Id() uint {
return a.id
}
...
// 这是类型约束
type Ider interface {
Id() uint
}
// 将一个 Ider 传递给 func generic
func generic[T Ider](t T) {
...
println(t.Id())
}
...
完整代码在此:
感谢回复。但如果我们引入 Ider 接口,generic 函数就无需使用泛型了,我们可以直接这样写:
func generic(t Ider) {
...
}
实际上,我有以下用例,我希望将 dump32/dump64 抽象为一个通用的 dump 函数:
package main
import "debug/pe"
func dump32(x *pe.OptionalHeader32) {
// 相同的逻辑
}
func dump64(x *pe.OptionalHeader64) {
// 相同的逻辑
}
func main() {
file,err := pe.Open("test.exe")
if err == nil {
switch x:=file.OptionalHeader.(type) {
case *pe.OptionalHeader64:
dump64(x)
case *pe.OptionalHeader32:
dump32(x)
default:
}
}
}
换个角度来看这个问题:
假设你的 func generic 在一个名为 genericfunc 的公共包中(导出为 func Generic)。其他人正在使用它。现在你决定更改函数内部的某些内容,例如将 println(t.Id) 这一行改为:
fmt.Println("%20d\n", t.Id)
因为你希望 ID 能够右对齐。这个更改对于你的结构体 a 和 b 来说效果很好。
与此同时,你包的其他用户编写了以下代码:
type c struct {
Id string
}
func main() {
genericfunc.Generic(c{Id: "y87Zo9pX6"})
}
这对于之前的 println(t.Id) 来说运行良好,但对于新的 fmt.Printf("%d",...) 就不太适用了。
一旦你的更改发布并被客户端导入,他们的代码将立即崩溃。
如果使用了类型约束,像这样的破坏性更改就不可能发生。
确实,仅使用一个 Ider 接口也能解决问题。
关键点:使用尽可能简单的解决方案。
如果你在泛型的上下文中寻找解决方案,要点在于 func generic 需要在编译时知道 t 的能力。T any 可以是任何类型,无论其是否包含 Id 字段,因此 t.Id 可能在运行时失败,所以是无效的。一个恰当的类型约束会告诉编译器 t 是一个内部包含 Id 的类型。
顺便说一下,我在之前的回复中犯了一个错误。我忽略了 a 和 b 包含不同的类型。对于这种情况,int 和 uint 的并集可以构建一个与 Ider 一起使用的类型约束,就像这段代码中一样。然而,正如 @hollowaykeanho 正确指出的那样,go2go playground 尚不支持并集语法,因此这段代码(目前?)无法运行。
话虽如此,我会选择非泛型、仅使用接口的解决方案。
编辑补充:对于你的特定用例,像 @hollowaykeanho 解决方案中那样使用类型开关似乎是可行的,或者考虑使用反射,类似于 go-spew 这样的转储工具所做的那样。
问题出在泛型约束上。你使用了 T any 作为约束,这意味着 T 可以是任何类型,但编译器无法保证类型 T 具有 Id 字段。
要解决这个问题,你需要定义一个接口约束来要求类型必须包含 Id 字段。以下是修正后的代码:
package main
import "reflect"
type a struct {
Id uint
}
type b struct {
Id int
}
// 定义接口约束
type HasId interface {
~struct { Id uint } | ~struct { Id int }
}
func generic[T HasId](t T) {
typ := reflect.TypeOf(t)
println(typ.String())
for idx := 0; idx < typ.NumField(); idx++ {
println(idx, typ.Field(idx).Name)
}
println(t.Id) // 现在可以正常工作
}
func main() {
generic(a{10})
generic(b{10})
}
或者,如果你希望更灵活地处理不同类型的 Id 字段,可以使用类型断言:
package main
import "reflect"
type a struct {
Id uint
}
type b struct {
Id int
}
// 定义包含Id方法的接口
type IDer interface {
GetID() interface{}
}
// 为a类型实现GetID方法
func (x a) GetID() interface{} {
return x.Id
}
// 为b类型实现GetID方法
func (x b) GetID() interface{} {
return x.Id
}
func generic[T IDer](t T) {
typ := reflect.TypeOf(t)
println(typ.String())
for idx := 0; idx < typ.NumField(); idx++ {
println(idx, typ.Field(idx).Name)
}
println(t.GetID()) // 通过接口方法访问Id
}
func main() {
generic(a{10})
generic(b{10})
}
在第一个修正方案中,HasId 约束使用类型集(type set)来指定 T 必须是包含特定 Id 字段的结构体。第二个方案则通过接口方法提供更通用的解决方案。

