Fifciuu
Fifciuu

Reputation: 864

Function that returns Slice of Custom Interface

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

Answers (2)

Adrian
Adrian

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

Eli Bendersky
Eli Bendersky

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

Related Questions