Reputation: 2343
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?
Upvotes: 89
Views: 51572
Reputation: 8245
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 Person
s. 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
.)
Upvotes: 2
Reputation: 73
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})
Upvotes: 3
Reputation: 53418
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
Upvotes: 131
Reputation: 57639
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()
Upvotes: 7
Reputation: 10857
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())
}
Upvotes: 8
Reputation: 91253
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.
Upvotes: 2