5

I am trying to define a generic function in Go that accepts values that have certain fields, for example, ID int. I have tried several approaches but none seems to work. Here is an example of what I have tried.

package main

import (
    "fmt"
)

func Print[T IDer](s T) {
    fmt.Print(s.ID)
}

func main() {
    Print(Person{3, "Test"})
}

type IDer interface {
    ~struct{ ID int }
}

type Person struct {
    ID   int
    Name string
}

type Store struct {
    ID     int
    Domain string
}

And here is the playground link: https://gotipplay.golang.org/p/2I4RsUCwagF

In the example above, I want to guarantee every value passed to the Print function has a property ID int, which is also accessible in the function. Is there any way I can achieve this in Go without defining a method in an interface (e.g., GetID() int)?

Zeke Lu
  • 6,349
  • 1
  • 17
  • 23
Behrooz
  • 2,181
  • 1
  • 16
  • 24

2 Answers2

6

Is there any way I can achieve this is Go without defining a method in an interface (e.g., GetID() int)?

No, you have to define the method in an interface.

The generics implementation in Go 1.18 doesn't have support for structural types, even though the original type parameters proposal suggests it would. For accessing common fields in a union instead see also this explanation.

Although, I think it's worth it to point out a misconception that can easily arise from your example: the meaning of the approximation ~T (tilde-type) means "the set of types whose underlying type is T.

Now, when you write:

~struct{ ID int }

this means types whose underlying type is exactly struct{ ID int }. No matter what, this does not include structs that have the field ID int and something else. E.g. the underlying type of type Foo struct { ID int; Name string } is struct { ID int; Name string }, and not struct{ ID int }, so that wouldn't satisfy the constraint anyway.

The current time param implementation doesn't have syntax to specify partial struct types. I recall a proposal to add field terms in interface constraints (along with type terms and methods), something on the line:

type IDer interface {
    ID int
}

which would enable what you are trying to do without breaking the meaning of the tilde ~. But this won't be included in Go 1.18.

blackgreen
  • 34,072
  • 23
  • 111
  • 129
0

As a workaround, you can provide getters for your fields. Then your type restriction can use an interface with that getter.

You can make it easy to add an ID getter by defining a WithID struct with ID() method. Here is a full example based on your code:

package main

import (
    "fmt"
)

type IDer interface {
    ID() int
}

func Print[T IDer](s T) {
    fmt.Print(s.ID())
}


type WithID int

func (id WithID) ID() int {
  return int(id)
}

type Person struct {
    WithID
    Name string
}

type Store struct {
    WithID
    Domain string
}

func main() {
    Print(Person{1, "Name"})
    Print(Store{2, "Domain"})
}

Playground: https://gotipplay.golang.org/p/-sG8XQGnySS

Vinicius Fortuna
  • 393
  • 4
  • 11