Reputation: 864
At first, I want to introduce you because I feel like I am missing some core Golang concept.
In my application, many models will have a method called GetByUserId. I created interface(UserCreatedEntity) that requires this method so then I will be able to create Controller's GetUserRecords method factory for each type of records with just:
router.Handle("/ideas/mine",
middlewares.AuthUser(controllers.GetMineFactory(&models.Idea{}))).Methods("POST")
router.Handle("/votes/mine",
middlewares.AuthUser(controllers.GetMineFactory(&models.Vote{}))).Methods("POST")
router.Handle("/someNewType/mine",
middlewares.AuthUser(controllers.GetMineFactory(&models.SomeNewType{}))).Methods("POST")
This is how my interface looks like:
type UserCreatedEntity interface {
GetByUserId(userId uint) []UserCreatedEntity
}
And implementation:
func (idea *Idea) GetByUserId(userId uint) []UserCreatedEntity {
ideas := []Idea{}
GetDB().
Table("ideas").
/** Query removed to make code less confusing **/
Scan(ideas)
return ideas
}
Obviously, it does not work (version with slice of pointers neither do). The thing is - This code would work if I return only one record - like that (obviously with changing signature in interface also):
func (idea *Idea) GetByUserId(userId uint) UserCreatedEntity {
idea := &Idea{}
GetDB().
Table("ideas").
/** Query removed to make code less confusing **/
First(idea)
return idea
}
How to make it work as slice? As I said I suspect that I am missing some important knowledge. So deep explaination would be awesome.
Solution:
func (idea *Idea) GetByUserId(userId uint) []UserCreatedEntity {
ideas := []*Idea{}
GetDB().
Table("ideas").
Select("problems.name AS problem_name, ideas.id, ideas.problem_id, ideas.action_description, ideas.results_description, ideas.money_price, ideas.time_price, ideas.is_published").
Joins("INNER JOIN problems ON ideas.problem_id = problems.id").
Where("ideas.user_id = ?", userId).
Scan(&ideas)
uces := make([]UserCreatedEntity, len(ideas))
for i, idea := range ideas {
uces[i] = idea
}
return uces
}
Upvotes: 0
Views: 346
Reputation: 46423
Interfaces are dynamic. Composite types that involve interfaces are not.
UserCreatedEntity
is an interface, and Idea
satisfies the interface, so you can return an Idea
from a function whose signature has a return type of UserCreatedEntity
.
[]UserCreatedEntity
is a slice of UserCreatedEntity
, not an interface. The only type that can be returned is []UserCreatedEntity
. []Idea
is a different type (slice of Idea
). You can fill a []UserCreatedEntity
with Idea
elements, because each element is of type UserCreatedEntity
, which again is an interface and Idea
is allowed there.
Similarly, func() UserCreatedEntity
is a type "function which returns UserCreatedEntity
". You cannot subsitute a func() Idea
because that is a different type. But you can return an Idea
from a func() UserCreatedEntity
because an Idea
is a UserCreatedEntity
.
If you weren't using Scan
here, which presumably uses reflection, the fix would be to declare your local slice as []UserCreatedEntity
instead of []Idea
. Since you are using Scan
, you instead must scan into a []Idea
, then iterate over it to copy all the elements to a []UserCreatedEntity
and return that.
Upvotes: 1
Reputation: 273376
In programming language theory this is called variance, and it is not supported in Go. For much more details see this proposal.
Specifically, return types are not covariant. A slice of T
does not implement a slice of I
even if T
implements I
.
The FAQ entry linked above proposes this workaround:
It is necessary to copy the elements individually to the destination slice. This example converts a slice of int to a slice of interface{}:
t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
s[i] = v
}
Though in your case the right solution may be different.
Upvotes: 4