结构体的切片 != 实现它的接口的切片?

huangapple go评论100阅读模式
英文:

slice of struct != slice of interface it implements?

问题

我有一个接口Model,由结构体Person实现。

为了获得一个模型实例,我有以下辅助函数:

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

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

上述方法允许我返回一个正确类型的Person实例(可以使用相同的方法轻松添加新模型)。

当我尝试类似的方式返回一个模型切片时,我遇到了错误。代码如下:

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

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

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

我的目标是返回一个请求的任何模型类型的切片(无论是[]Person[]FutureModel[]Terminator2000等)。我漏掉了什么,如何正确实现这样的解决方案?

英文:

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?

答案1

得分: 129

这与我刚刚回答的一个问题非常相似:https://stackoverflow.com/a/12990540/727643

简短的答案是你是正确的。一个结构体切片与一个接口切片是不相等的,即使结构体实现了该接口。

[]Person[]Model有不同的内存布局。这是因为它们所包含的类型具有不同的内存布局。Model是一个接口值,这意味着在内存中它占用两个字长的空间。一个字长用于类型信息,另一个字长用于数据。Person是一个结构体,其大小取决于它包含的字段。为了将[]Person转换为[]Model,你需要遍历数组并对每个元素进行类型转换。

由于这个转换是一个O(n)的操作,并且会创建一个新的切片,Go不会隐式地执行它。你可以使用以下代码显式地执行它。

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

正如dskinner指出的,你很可能想要一个指针切片而不是一个指向切片的指针。通常不需要指向切片的指针。

*[]Person        // 指向切片的指针
[]*Person        // 指针切片
英文:

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

答案2

得分: 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())
}
英文:

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())
}

答案3

得分: 7

由于Stephen已经回答了这个问题,而你是一个初学者,我强调给出建议。

在处理go的接口时,更好的方式不是像其他语言(如Java)那样有一个返回接口的构造函数,而是为每个对象单独提供一个构造函数,因为它们隐式地实现了接口。

不要使用

newModel(type string) Model { ... }

而应该使用

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

其中PersonPolitician都实现了Model的方法。
你仍然可以在任何接受Model的地方使用PersonPolitician,但你也可以实现其他接口。

使用你的方法,你将被限制在Model上,直到你手动转换为另一个接口类型。

假设我有一个实现了Walk()方法的Person和一个实现了ShowOff()方法的Model,以下代码将无法直接工作:

newModel("person").ShowOff()
newModel("person").Walk() // 无法编译,Model没有Walk方法

然而,以下代码可以工作:

newPerson().ShowOff()
newPerson().Walk()
英文:

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()

答案4

得分: 3

正如其他人已经回答的那样,[]T是一种独特的类型。我只想补充一点,可以使用一个简单的工具来通用地将它们转换。

import "reflect"

// 将特定类型的切片或数组转换为interface{}类型的数组
func ToIntf(s interface{}) []interface{} {
    v := reflect.ValueOf(s)
    // 没有必要进行检查,如果不是切片或数组,我们希望引发panic
    intf := make([]interface{}, v.Len())
    for i := 0; i < v.Len(); i++ {
        intf[i] = v.Index(i).Interface()
    }
    return intf
}

现在,你可以像这样使用它:

ToIntf([]int{1,2,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 &quot;reflect&quot;

// 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&#39;s not slice or array
	intf := make([]interface{}, v.Len())
	for i := 0; i &lt; v.Len(); i++ {
		intf[i] = v.Index(i).Interface()
	}
	return intf
}

Now, you can use it like this:

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

答案5

得分: 2

类型T和[]T是不同的类型,它们的方法也是不同的,即使它们满足相同的接口。换句话说,每个满足Model接口的类型必须自己实现Model的所有方法 - 方法的接收者只能是一个特定的类型。

英文:

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.

答案6

得分: 2

即使Go的实现允许这样做,不幸的是这是不安全的:你不能将[]Person赋值给类型为[]Model的变量,因为[]Model具有不同的能力。例如,假设我们还有实现了ModelAnimal

var people []Person = ...
var models []Model = people // 在真正的Go中不允许
models[0] = Animal{..} // ???
var person Person = people[0] // !!!

如果我们允许第2行,那么第3行也应该可以工作,因为models可以完全存储一个Animal。而第4行仍然应该工作,因为people存储的是Person。但是,我们最终得到的是一个类型为Person的变量持有一个Animal

Java实际上允许类似于第2行的操作,但这被广泛认为是一个错误。(错误在运行时被捕获;第3行将抛出ArrayStoreException。)

英文:

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.)

huangapple
  • 本文由 发表于 2012年10月21日 11:26:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/12994679.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定