如何访问具有泛型的结构字段(类型 T 没有字段或方法)?

How can I access a struct field with generics (type T has no field or method)?

提问人:Marc-François 提问时间:12/15/2021 最后编辑:blackgreenMarc-François 更新时间:10/29/2022 访问量:8929

问:

我想编译以下代码。我从阅读类型参数提案(Go Generics)中得到的理解是,这应该有效,但我一定遗漏了一些东西。

package main

import "fmt"

func main() {
    s := Struct{A: "Hello World!"}
    PrintA(s)
}

func PrintA[T Type](v T) {
    fmt.Printf("%s\n", v.A)
}

type Type interface {
    struct{ A string }
}

type Struct struct {
    A string
}

func (s Struct) String() string {
    return s.A
}

我得到的错误是:

./prog.go:7:8:结构体未实现类型(约束类型中的struct{A string}可能缺少~) ./prog.go:11:23:v.A 未定义(类型 T 没有字段或方法 A)

我想用特定类型的特定字段表示所有结构。添加无济于事。T~

这是已实现的提案中的一个示例,该提案是最新 Go 测试版的一部分。

type structField interface {
    struct { a int; x int } |
        struct { b int; x float64 } |
        struct { c int; x uint64 }
}

https://go.dev/play/p/KZh2swZuD2m?v=gotip

Go 泛型 字段

评论


答:

27赞 blackgreen 12/15/2021 #1

在 Go 1.18 中禁用了字段访问(在 Go 1.19 中仍处于禁用状态)。Go 1.18 发行说明中提到了这一点:

当前的泛型实现具有以下已知限制:

[...]

  • Go 编译器不支持访问类型参数类型的结构字段,即使类型参数的类型集中的所有类型都具有字段 f。我们可能会在 Go 1.19 中删除此限制。x.fx

任何结构类型的解决方法都可以归结为基于接口的冗长无聊的多态性:

type Type interface {
    GetA() string
}

func (s Struct) GetA() string {
    return s.A
}

在这一点上,您甚至不必使用接口作为约束。它可以只是一个普通的接口类型:Type

func PrintA(v Type) {
    fmt.Printf("%s\n", v.GetA())
}

如果你只把这个接口作为一个约束,你可以添加类型元素来限制哪些结构可以实现它:

type Type interface {
    StructFoo | StructBar
    GetA() string
}

如果使用指针接收器声明了方法,请使用指针类型。


旧答案(不再相关,仅提供信息)

在 2022 年初的某个时候,当此功能仍在开发中时,如果您添加以下内容,您的示例确实有效:~

type Type interface {
    ~struct{ A string }
}

但它只适用于完全定义为的结构,没有其他任何内容。定义一个约束,即“用特定类型的特定字段表示所有结构”,从来都不支持。有关详细信息,请参阅此答案struct{ A string }

相反,您从提案中引用的示例是关于访问类型集中的公共字段。通过定义结构的联合:

type structField interface {
    ~struct { a int; x int } | ~struct { a int; x float64 } 
}

您应该能够访问此类类型参数的字段,但同样没有实现,如答案开头所述。如果联合中的所有术语都具有相同的基础类型,则它曾经起作用(示例改编自问题 #48522)。a

自 2022 年 3 月起,此代码不再编译

package main

import "fmt"

type Point struct {
    X, Y int
}

type Rect struct {
    X, Y int
}

func GetX[P Point | Rect] (p P) int {
    return p.X
}

func main() {
    p := Point{1, 2}
    r := Rect{2, 3}
    fmt.Println("X: %d %d", GetX(p), GetX(r)) // prints X: 1 2
}

评论

0赞 Marko 6/30/2023
一个非常好的答案。在文档引用中,它说暗示他们甚至可以在 Go 1.18 中启用它。为什么他们不允许在 Go 1.18 中这样做?We may remove this restriction in Go 1.19.
2赞 blackgreen 7/1/2023
@Marko谢谢!tl;DR 是他们没有时间考虑启用结构字段访问的所有可能后果,一旦功能正式发布,他们就无法在不破坏兼容性的情况下将其收回。他们想谨慎行事。然后在 1.18 之后,其他事情获得了优先权。
0赞 pythonator 11/2/2023
从 v1.21 开始仍然如此 :(
0赞 VonC 11/3/2023
注意:可悲的是,问题 48522 是......可能下降
0赞 VonC 11/15/2023
实际上,拒绝了 63940:“规范:调查我们是否可以消除对核心类型概念的(大部分或全部)需求”