Reputation: 3469
I've just seen Go has incorporated generics in its latest release, and I'm trying to create a small project to understand how it works. I don't seem to figure out how it works apart from very simple functions being now generic. I'd like to be able to do things like this:
type Dao[RT any] interface {
FindOne(id string) *RT
}
type MyDao struct {
}
type ReturnType struct {
id int
}
func (m *MyDao) FindOne(id string) *ReturnType {
panic("implement me")
}
// how should this look like?
func NewMyDao() *Dao[ReturnType] {
return &MyDao[ReturnType]{}
}
Is that even possible? I don't seem to be implementing the interface that way, and I've tried many combinations of the same.
Is there a way to implement a generic interface? If not, is the alternative only to return the interface{}
type?
Upvotes: 39
Views: 76908
Reputation: 44675
Types don't actually implement generic interfaces, they implement instantiations of generic interfaces. You can't use a generic type (including interfaces) without instantiation. From there, it is just like pre-generics Go, including the difference between methods with pointer receiver.
Therefore it is helpful to think what the methods that use type parameters would look like if you rewrote them with concrete types.
Let's consider a generic interface and some type:
type Getter[T any] interface {
Get() T
}
type MyStruct struct {
Val string
}
There's a few possible cases
Instantiate as Getter[string]
, implemented by types with method Get() string
// implements Getter[string]
func (m MyStruct) Get() string {
return m.Val
}
// ok
func foo() Getter[string] {
return MyStruct{}
}
Functions that have type parameters may use those to instantiate generic types, e.g. Getter[T]
. Implementors must have exactly the Get() T
method. For that to be valid, they are also generic and instantiated with the same type parameter:
So this doesn't compile even if T
is string
// Getter[T] literally needs implementors with `Get() T` method
func bar[T any]() Getter[T] {
return MyStruct{} // doesn't compile, even if T is string
}
Making MyStruct
also parametrized works:
type MyStruct[T any] struct {
Val T
}
func (m MyStruct[T]) Get() T {
return m.Val
}
func bar[T any]() Getter[T] {
return MyStruct[T]{} // ok
}
Let's reverse the previous cases. We keep the parametrized MyStruct[T any]
but now the interface is not parametrized:
type Getter interface {
Get() string
}
In this case, MyStruct
implements Getter
only when it is instantiated with the necessary concrete type:
// Getter requires method `Get() string`
func baz() Getter {
return MyStruct[string]{} // instantiate with string, ok
// return MyStruct[int]{} // instantiate with something else, doesn't compile
}
This follows the same rules as above, but requires instantiating pointer types, as usual:
// pointer receiver, implements Getter[string]
func (m *MyStruct) Get() string {
return m.Val
}
func foo() Getter[string] {
return &MyStruct{} // ok
// return MyStruct{} // doesn't implement
}
and it is the same if MyStruct
is generic.
// parametrized pointer receiver
func (m *MyStruct[T]) Get() T {
return m.Val
}
func foo() Getter[string] {
return &MyStruct[string]{} // ok
}
So in your case, the mental exercise of replacing the type params with concrete types gives that Dao[ReturnType]
has method FindOne(id string) *ReturnType
. The type that implements this method is *MyDao
(pointer receiver), therefore:
func NewMyDao() Dao[ReturnType] {
return &MyDao{}
}
Upvotes: 79
Reputation: 51567
The type *MyDao
implements the interface Dao[ReturnType]
. Thus, the function should look like:
func NewMyDao() Dao[ReturnType] {
return &MyDao{}
}
Note that the return type is an instance of the generic interface, and the returned value is simply an instance of the *MyDao
type.
Upvotes: 4