87

I have an interface Model, which is implemented by struct Person.

To get a model instance, I have the following helper functions:

func newModel(c string) Model {
    switch c {
    case "person":
        return newPerson()
    }
    return nil
}

func newPerson() *Person {
    return &Person{}
}

The above approach allows me to return a properly typed Person instance (can easily add new models later with same approach).

When I attempted to do something similar for returning a slice of models, I get an error. Code:

func newModels(c string) []Model {
    switch c {
    case "person":
        return newPersons()
    }
    return nil
}

func newPersons() *[]Person {
    var models []Person
    return &models
}

Go complains with: cannot use newPersons() (type []Person) as type []Model in return argument

My goal is to return a slice of whatever model type is requested (whether []Person, []FutureModel, []Terminator2000, w/e). What am I missing, and how can I properly implement such a solution?

Dave C
  • 7,729
  • 4
  • 49
  • 65
Jon L.
  • 2,292
  • 2
  • 19
  • 31
  • 3
    A slice is different from an array in Go. Since you were really talking about slices, I edited your post to reflect this. – Stephen Weinberg Oct 21 '12 at 04:15
  • Stephen, thanks, appreciated :-) – Jon L. Oct 21 '12 at 04:23
  • @JonL. Did you ever figure this out? I'm trying to do the same thing so that I don't have to repeat a ton of code for my `/api/{collection}`. I have it working for everything except for an index function that needs to read into a slice. – Derek Perkins Jul 25 '14 at 03:44
  • @DerekPerkins, I don't recall what I ended up doing here, and I haven't played w/ Go in awhile. Sorry I can't be of more assistance. – Jon L. Jul 30 '14 at 13:27
  • Contravariance (or is that covariance?) strikes again! – David Jul 19 '18 at 01:08

6 Answers6

130

This is very similar to a question I just answered: https://stackoverflow.com/a/12990540/727643

The short answer is that you are correct. A slice of structs is not equal to a slice of an interface the struct implements.

A []Person and a []Model have different memory layouts. This is because the types they are slices of have different memory layouts. A Model is an interface value which means that in memory it is two words in size. One word for the type information, the other for the data. A Person is a struct whose size depends on the fields it contains. In order to convert from a []Person to a []Model, you will need to loop over the array and do a type conversion for each element.

Since this conversion is an O(n) operation and would result in a new slice being created, Go refuses to do it implicitly. You can do it explicitly with the following code.

models := make([]Model, len(persons))
for i, v := range persons {
    models[i] = Model(v)
}
return models

And as dskinner pointed out, you most likely want a slice of pointers and not a pointer to a slice. A pointer to a slice is not normally needed.

*[]Person        // pointer to slice
[]*Person        // slice of pointers
Community
  • 1
  • 1
Stephen Weinberg
  • 51,320
  • 14
  • 134
  • 113
  • 1
    Didn't realize at the time, but my answer doesn't directly address the issue raised. This answer addresses it. – dskinner Oct 21 '12 at 04:12
  • 1
    Very nice answer, especially with the explanation about the number of words of memory for each type – I82Much Oct 21 '12 at 17:16
  • 3
    Well how do you write something in an abstract, generic way then? Imagine you have 100k results from a database query and you have to iterate over them all and create a copy of each dataset. This means you can't have generic interfaces in Go –  Apr 26 '17 at 14:58
  • Your answer is correct, but the explanation made me think that if I had an interface `A` which implements my desired interface `B` I would be able to use a slice of `A`s as a slice of `B`s (after all, the memory structure is identical, since they're both interfaces). I was disappointed to discover I can't :( – Noam Nelke Mar 04 '19 at 10:08
  • 1
    Interface A and interface B have different vtables. So it is not quite the same layout in memory. If it treated interface A the same way it treated interface B, it might try to execute the wrong function. You can use unsafe.Pointer to try this out yourself. It would be an interesting experiment! – Stephen Weinberg Mar 06 '19 at 02:16
8

Maybe this is an issue with your return type *[]Person, where it should actually be []*Person so to reference that each index of the slice is a reference to a Person, and where a slice [] is in itself a reference to an array.

Check out the following example:

package main

import (
    "fmt"
)

type Model interface {
    Name() string
}

type Person struct {}

func (p *Person) Name() string {
    return "Me"
}

func NewPersons() (models []*Person) {
    return models
}

func main() {
    var p Model
    p = new(Person)
    fmt.Println(p.Name())

    arr := NewPersons()
    arr = append(arr, new(Person))
    fmt.Println(arr[0].Name())
}
dskinner
  • 10,527
  • 3
  • 34
  • 43
  • Thanks for the response. While (as you noted) it doesn't directly address the issue at hand, it's an appreciated example nonetheless :-) – Jon L. Oct 21 '12 at 04:27
7

As Stephen already answered the question and you're a beginner I emphasize on giving advises.

A better way of working with go's interfaces is not to have a constructor returning the interface as you might be used to from other languages, like java, but to have a constructor for each object independently, as they implement the interface implicitly.

Instead of

newModel(type string) Model { ... }

you should do

newPerson() *Person { ... }
newPolitician() *Politician { ... }

with Person and Politician both implementing the methods of Model. You can still use Person or Politician everywhere where a Model is accepted, but you can also implement other interfaces.

With your method you would be limited to Model until you do a manual conversion to another interface type.

Suppose I have a Person which implements the method Walk() and a Model implements ShowOff(), the following would not work straight forward:

newModel("person").ShowOff()
newModel("person").Walk() // Does not compile, Model has no method Walk

However this would:

newPerson().ShowOff()
newPerson().Walk()
nemo
  • 55,207
  • 13
  • 135
  • 135
  • You're correct :-( My approach is my attempt to generically provide access to models via a ReST API. So a request to `/api/{collection}` would dynamically interact w/ the requested collection. Can you suggest an alternative solution without explicitly testing the requested collection across multiple functions? I guess what I'm looking for is a way to specify a generic return type without losing knowledge of it's type. – Jon L. Oct 21 '12 at 04:45
  • 2
    Knowledge of the type is always preserved, even if you return `interface{}`, the problem is that you're then need to do runtime type assertions instead of compiler type checking. A generic solution for `name -> object` without runtime assertions is only possible using generics, which go does not support. So if you do this, you have to live with reflection or the drawbacks of your solution. – nemo Oct 21 '12 at 14:13
  • 1
    point is []Model != []Person or even []Model != []Politician. That's the issue. –  Apr 26 '17 at 15:05
  • @dalu Correct. I mention this at the top of my answer as well: this answer gives complementary information regarding typing and go interface conventions that the OP did not seem to know about but the accepted answer did not address. – nemo Apr 26 '17 at 17:07
3

As others have already answered, []T is a distinct type. I'd just like to add that a simple utility can be used to convert them generically.

import "reflect"

// Convert a slice or array of a specific type to array of interface{}
func ToIntf(s interface{}) []interface{} {
    v := reflect.ValueOf(s)
    // There is no need to check, we want to panic if it's not slice or array
    intf := make([]interface{}, v.Len())
    for i := 0; i < v.Len(); i++ {
        intf[i] = v.Index(i).Interface()
    }
    return intf
}

Now, you can use it like this:

ToIntf([]int{1,2,3})
slavikm
  • 73
  • 3
2

Types T and []T are distinct types and distinct are their methods as well, even when satisfying the same interface. IOW, every type satisfying Model must implement all of the Model's methods by itself - the method receiver can be only one specific type.

zzzz
  • 87,403
  • 16
  • 175
  • 139
2

Even if Go's implementation allowed this, it's unfortunately unsound: You can't assign a []Person to a variable of type []Model because a []Model has different capabilities. For example, suppose we also have Animal which implements Model:

var people []Person = ...
var models []Model = people // not allowed in real Go
models[0] = Animal{..} // ???
var person Person = people[0] // !!!

If we allow line 2, then line 3 should also work because models can perfectly well store an Animal. And line 4 should still work because people stores Persons. But then we end up with a variable of type Person holding an Animal!

Java actually allows the equivalent of line 2, and it's widely considered a mistake. (The error is caught at run time; line 3 would throw an ArrayStoreException.)

Luke Maurer
  • 7,845
  • 2
  • 24
  • 24