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

1

22

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)
./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

Kerns answered 15/12, 2021 at 3:44 Comment(0)
A
34

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("%s\n", 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.


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[s] 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 "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
}
Albania answered 15/12, 2021 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.