How can I define a struct field in my interface as a type constraint?

Issue

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 (interface Type has no 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

Solution

Field access has been completely disabled for Go 1.18. 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 the old boring interface-based polymorphism that we all have been using so far without type params:

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

Old answer

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):

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
}

This code doesn’t compile anymore as of March 2022.

Answered By – blackgreen

Answer Checked By – Pedro (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.