Home > Mobile >  Golang: How to embed interface with different type parameters?
Golang: How to embed interface with different type parameters?

Time:10-05

The code gives me error: DB redeclared. Is there any idiomatic way to solve it? Or any work-around?

TIA

type a struct {
    DB[int64]
    DB[string]
}

type b interface {
    DB[int64]
    DB[string]
}

type DB[T any] interface {
    GetList(query string) ([]T, error)
}

CodePudding user response:

You can't embed the same interface, even with different type parameters. Regardless of how it is instantiated, you are trying to promote into the b interface two methods with the same name GetList and different signatures — given by the different instantiations of DB.

The situation is similar, although not technically the same, for embedding into a struct. In structs, the name of the embedded field is the name of the type — DB —, and a struct can't have two non-blank fields with the same name.

About how to solve this issue, it depends what you want to accomplish.

If you want to convey that "a implements DB with either type parameter" you can embed DB[T] and make a itself generic, and restrict a's type parameters:

type a[T int64 | string] struct {
    DB[T]
}

// illustrative implementation of the DB[T] interface
// if you embed DB[T] you likely won't use `a` itself as receiver
func (r *a[T]) GetList(query string) ([]T, error) {
    // also generic method
}

This is okay because DB's type parameter is constrained to any, and a's type parameter is more restrictive. This allows you to use a in other generic methods, or choose a specific type upon instantiation, but the implementation of GetList has to be parametrized too.

Otherwise if you need a to have separated methods that return int64 or string, you must give it different names.

Finally, you can embed instances of DB into two interfaces with different names, and then embed those into a instead.

type a struct {
    DBStr
    DBInt
}

type DBStr interface {
    DB[string]
}

type DBInt interface {
    DB[int64]
}

This way though the top-level selector isn't available because the method names are still the same. The program compiles, but you'll have to explicitly choose which field to call the method on:

myA := a{ /* init the two fields */ }

res, err = myA.DBStr.GetList(query)
// res is type []string
// or
res, err = myA.DBInt.GetList(query)
// res is type []int64
  • Related