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

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

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

问题

我想让以下代码编译通过。根据我阅读的类型参数提案(Go泛型),我认为这应该可以工作,但我可能漏掉了什么。

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 does not implement Type (possibly missing ~ for struct{A string} in constraint Type)
./prog.go:11:23: v.A undefined (type T has no field or method A)

我希望T表示具有特定类型的特定字段的所有结构体。添加~并没有帮助。

以下是提案中的一个已实现并包含在最新的Go beta版本中的示例。

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

英文:

I would like to make the following code compile. My understanding from reading the Type Parameters Proposal (Go Generics) is that this should work, but I must be missing something.

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
}

The error I get is:

> ./prog.go:7:8: Struct does not implement Type (possibly missing ~ for struct{A string} in constraint Type)<br>
> ./prog.go:11:23: v.A undefined (type T has no field or method A)

I would like T to represent all structs with a particular field of a particular type. Adding ~ did not help.

Here's an example from the proposal that was implemented and is part of the latest Go beta release.

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

答案1

得分: 26

字段访问在Go 1.18中已被禁用(在Go 1.19中仍然禁用)。Go 1.18发布说明中提到了这一点:

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

[...]

  • 即使类型参数的类型集中的所有类型都有一个字段f,Go编译器也不支持访问结构字段x.f,其中x是类型参数类型。我们可能会在Go 1.19中取消此限制。

任何结构类型的解决方法都归结为旧的、乏味的基于接口的多态性:

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

应该能够访问此类类型参数的字段a,但是正如答案开头提到的,这并没有实现。如果联合中的所有项具有相同的基础类型,它曾经可以工作(示例改编自issue #48522)。

截至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)) // 打印 X: 1 2
}
英文:

Field access has been disabled for Go 1.18 (still disabled in Go 1.19). The Go 1.18 release notes mention this:

> The current generics implementation has the following known limitations:
>
> [...]
>
> - The Go compiler does not support accessing a struct field x.f where x is of type parameter type even if all types in the type parameter's type set have a field f. We may remove this restriction in Go 1.19.

The workaround for any struct type boils down to old boring interface-based polymorphism:

type Type interface {
    GetA() string
}

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

And at this point you don't even have to use the Type interface as a constraint. It can just be a plain interface type:

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

If you are ok with using this interface only as a constraint, you may add type elements to restrict which structs can implement it:

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

Use pointer types if you declared the methods with pointer receiver.

<hr>

Old answer (not relevant anymore, only informative)

At some point in early 2022 while this feature was still in development, your example did work if you added ~:

type Type interface {
    ~struct{ A string }
}

but it only worked for structs exactly defined as struct{ A string } and nothing else. Defining a constraint that "represent

展开收缩
all structs with a particular field of a particular type" was never supported all along. See this answer for details.

Instead the example you quote from the proposal is about accessing a common field in a type set. By defining a union of structs:

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

you should be able to access the field a of such a type parameter, but again this wasn't implemented, as mentioned at the beginning of the answer. It used to work if all terms in the union had the same underlying type (example adapted from issue #48522).

This code doesn't compile anymore as of March 2022:

package main

import &quot;fmt&quot;

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(&quot;X: %d %d&quot;, GetX(p), GetX(r)) // prints X: 1 2
}

huangapple
  • 本文由 发表于 2021年12月15日 11:44:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/70358216.html
匿名

发表评论

匿名网友

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

确定