Generic typing of slices in Go

Issue

Rather than having multiple API fetching functions, I want to make one function that can fetch the appropriate database, format it into a slice of structs, and return it. Below is a simplified version of my code:

type Person struct {
        Name   string `json:"name"`
        IsCool bool   `json:"is_cool"`
}

type Pet struct {
        Name     string `json:"name"`
        IsFluffy bool   `json:"is_fluffy"`
}

type Group struct {
        Name         string    `json:"name"`
        CreationDate time.Time `json:"creation_date"`
}

type generic interface{}

func FetchDB(dbName string) interface {
        var collection []generic
        var model      generic

        switch dbName {
        case "users":
                collection = new([]User)
                model      = new(User)
        }
        case "pets":
                collection = new([]Pet)
                model      = new(Pet)
        case "groups":
                collection = new([]Group)
                model      = new(Group)
        default:
                return nil
        }

        iter := client.DB(dbName).Get()

        for {
                entry, err := iter.Next()
                if err != nil {
                        break
                }
                entry.ToStruct(&model)

                collection = append(collection, model)
        }

        return collection
}

However, calling this function results in cannot use new([]User) (type *[]User) as type []generic in assignment. My first question is how I can create a “generic” slice, and assign a type to it. My second question is whether this is a correct way to set up this function—using the generic interface feels hacky!—and if it is not, what would be a more sensible way to then design this function.

Solution

You are using new, not make for slices. Those two are different. new allocates memory for the type (in this case, the slice, not the contents of the slice), whereas make makes an instance of that type (make([]User,0) will make a slice of Users).

You can make this work as follows:

collection:=make([]generic,0)
switch dbName {
   case "users":
       model      = new(User)
   case "pets":
       model      = new(Pet)
   case "groups":
       model      = new(Group)
   default:
       return nil
}

And your model is already a pointer and you want to store data where that pointer points to:

  entry.ToStruct(model)  // Not &model

You’re returning an interface{}, instead you can return []generic, that saves you from one type assertion to get the array part of the value. But that’s when your problems will start because there are no generic in Go, and you’ll have to write many type assertions to get to the data you need.

There are several ways this can be done. One approach would be a callback function to collect data:

func FetchDB(dbName string, collector func(entry Entry) {
  iter := client.DB(dbName).Get()
  for {
        entry, err := iter.Next()
        collector(entry)
   }
}

And call it as:

users:=make([]User,0)
FetchDB("users",func(entry Entry) {
   var u User
   entry.ToStruct(&u)
   users=append(users,u) 
 })

Answered By – Burak Serdar

Answer Checked By – Timothy Miller (GoLangFix Admin)

Leave a Reply

Your email address will not be published.