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?
CodePudding user response:
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.
CodePudding user response:
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
Interface with concrete type argument
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{}
}
Interface with type parameter as type argument
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
}
Concrete interface with generic implementor
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
}
Pointer receivers
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{}
}