gostructinterfacecomposition

Generics: constraints and struct embedded in interface


The following code does not compile, and reports ./main.go:35:7: Test does not satisfy Constraint (Test missing in main.StructWithName)

package main

import "fmt"

type Test struct {
    Name string
}

func (t Test) String() string {
    return fmt.Sprintf("Name: %v", t.Name)
}

type StructWithName struct {
    Name string
}

type Constraint interface {
    StructWithName
    // ~struct {
    //  Name string
    // }
    String() string
}

func Print[T Constraint](x T) {
    //s := T{}
    fmt.Printf("Hello %v", x)
}

func main() {
    t := Test{
        Name: "Test",
    }

    Print(t)
}

However, if I comment out the StructWithName and uncomment the ~struct block, then it compiles fine. I can't wrap my head around the semantic difference between both.

What I was trying to do was to have a generic method that could handle structs in a generic way; i.e. x := S{} then pass it along to an external library. In this case, it's for a Terraform provider, the code in the resources are very similar and I'm a bit troubled by the amount of repetition I see in exiting providers; in my case resources are extremely similar, some just have one or two fields on top of the default structure). I was hoping to be able to write a set of generic methods to do all that is similar and only specificities somewhere else (duh!).

I'm still early in my go journey and I can't make sense of the compiler error.


Solution

  • What you actually want, if I understood you correctly, is not possible. Go generics do not support access to struct field subsets.


    As far as the error in question is concerned...

    The name of a type specifies the type's identity and two types with different names (which are not aliases of each other) are always distinct, so Test and StructWithName are two distinct types. The only thing they have in common is their underlying type.

    The type Constraint interface { StructWithName } interface is a type set that contains only one type, namely StructWithName, and since Test is not a member of that type set this means that it's impossible to satisfy that constraint with the type Test.

    Type struct { Name string } is an unnamed type, the underlying type of any unnamed type is the type itself, i.e. the underlying type of struct { Name string } is struct { Name string }.

    The tilde in front of a type in a constraint, i.e. ~T, means any type with underlying type identical to T. Or, more precisely,

    The type set of a term of the form ~T is the set of all types whose underlying type is T.

    So when you do

    type Constraint interface { ~struct { Name string } }
    

    then the constraint's type set will contain any type that has struct { Name string } as its underlying type.