关于“X没有实现Y(…方法有一个指针接收器)之类的东西,但对我来说,它们似乎在谈论不同的事情,并不适用于我的具体情况。

所以,我没有把问题弄得非常具体,而是把它弄得广泛而抽象——似乎有几种不同的情况会导致这个错误,有人能总结一下吗?

也就是说,如何避免问题,如果发生了,有什么可能性?谢谢。


当你试图将一个具体类型赋值或传递(或转换)给一个接口类型时,会出现这个编译时错误;而且类型本身并不实现接口,只是一个指向类型的指针。

简单总结:如果被赋值的值实现了被赋值的接口,那么对接口类型变量的赋值是有效的。如果它的方法集是接口的超集,它就实现它。指针类型的方法集包括具有指针和非指针接收器的方法。非指针类型的方法集只包括具有非指针接收器的方法。

让我们来看一个例子:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

func (m *MyType) String() string { return m.value }

Stringer接口类型只有一个方法:String()。任何存储在接口值Stringer中的值都必须具有此方法。我们还创建了一个MyType,并创建了一个带有指针接收器的MyType. string()方法。这意味着String()方法在*MyType类型的方法集中,但不在MyType类型的方法集中。

当我们尝试将MyType的值赋值给Stringer类型的变量时,我们会得到问题中的错误:

m := MyType{value: "something"}

var s Stringer
s = m // cannot use m (type MyType) as type Stringer in assignment:
      //   MyType does not implement Stringer (String method has pointer receiver)

但如果我们尝试将类型为*MyType的值分配给Stringer,一切都是ok的:

s = &m
fmt.Println(s)

我们得到了预期的结果(在Go Playground上试试):

something

所以得到这个编译时错误的要求是:

被赋值(或传递或转换)的非指针具体类型的值 被赋值给(或传递给或转换给)的接口类型 具体类型具有接口所需的方法,但带有指针接收器

解决问题的可能方法:

必须使用指向该值的指针,其方法集将包含指针接收器的方法 或者必须将接收方类型更改为非指针,因此非指针具体类型的方法集也将包含该方法(从而满足接口)。这可能是可行的,也可能是不可行的,因为如果方法必须修改值,那么非指针接收器就不是一个选项。

结构和嵌入

在使用结构和嵌入时,通常不是“你”实现接口(提供方法实现),而是你嵌入到结构中的类型。比如下面这个例子:

type MyType2 struct {
    MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: m}

var s Stringer
s = m2 // Compile-time error again

同样,编译时错误,因为MyType2的方法集不包含嵌入MyType的String()方法,只有*MyType2的方法集,所以下面的工作(在Go Playground上尝试它):

var s Stringer
s = &m2

我们也可以让它工作,如果我们嵌入*MyType并且只使用非指针MyType2(在Go Playground上尝试一下):

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = m2

此外,无论我们嵌入什么(MyType或*MyType),如果我们使用指针*MyType2,它将始终工作(在Go Playground上尝试它):

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = &m2

规范中的相关章节(来自章节Struct types):

给定一个结构类型S和一个名为T的类型,提升的方法被包括在结构的方法集中,如下所示: 如果S包含匿名字段T,则S和*S的方法集都包含包含接收者T的提升方法,*S的方法集也包含包含接收者*T的提升方法。 如果S包含匿名字段*T,则S和*S的方法集都包含带有接收者T或*T的提升方法。

换句话说:如果我们嵌入了一个非指针类型,非指针嵌入器的方法集只获得带有非指针接收器的方法(来自嵌入类型)。

如果我们嵌入一个指针类型,非指针嵌入器的方法集将获得带有指针和非指针接收器的方法(来自嵌入类型)。

如果我们使用指向嵌入器的指针值,不管嵌入类型是否为指针,指向嵌入器的指针的方法集总是获得带有指针和非指针接收器的方法(来自嵌入类型)。

注意:

有一种非常类似的情况,即当您有一个接口值包装MyType的值时,您尝试从它中类型断言另一个接口值Stringer。在这种情况下,由于上面描述的原因,断言将不成立,但我们得到一个略有不同的运行时错误:

m := MyType{value: "something"}

var i interface{} = m
fmt.Println(i.(Stringer))

运行时恐慌(在Go Playground上试试):

panic: interface conversion: main.MyType is not main.Stringer:
    missing method String

尝试转换而不是assert类型,我们会得到我们正在谈论的编译时错误:

m := MyType{value: "something"}

fmt.Println(Stringer(m))

我见过的另一种情况是,如果我想创建一个接口,其中一些方法将修改内部值,而另一些则不会。

type GetterSetter interface {
   GetVal() int
   SetVal(x int) int
}

然后实现这个接口的东西可以是这样的:

type MyTypeA struct {
   a int
}

func (m MyTypeA) GetVal() int {
   return a
}

func (m *MyTypeA) SetVal(newVal int) int {
    int oldVal = m.a
    m.a = newVal
    return oldVal
}

实现类型可能会有一些方法是指针接收器,一些不是,因为我有很多这些不同的东西是gettersetter,我想在测试中检查它们是否都在做预期的事情。

如果我这样做:

myTypeInstance := MyType{ 7 }
... maybe some code doing other stuff ...
var f interface{} = myTypeInstance
_, ok := f.(GetterSetter)
if !ok {
    t.Fail()
}

然后我就不会得到前面提到的“X没有实现Y (Z方法有指针接收器)”错误(因为这是一个编译时错误),但是我将有一个糟糕的一天来追踪为什么我的测试失败……

相反,我必须确保我使用指针进行类型检查,例如:

var f interface{} = new(&MyTypeA)
 ...

Or:

myTypeInstance := MyType{ 7 }
var f interface{} = &myTypeInstance
...

那么测试结果就皆大欢喜了!

但是等等!在我的代码中,也许我在某处有接受GetterSetter的方法:

func SomeStuff(g GetterSetter, x int) int {
    if x > 10 {
        return g.GetVal() + 1
    }
    return g.GetVal()
}

如果我从另一个类型方法内部调用这些方法,这将生成错误:

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

以下调用中的任何一个都可以工作:

func (m *MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}

为了保持简洁,假设你有一个Loader接口和一个实现该接口的WebLoader。

package main

import "fmt"

// Loader defines a content loader
type Loader interface {
    load(src string) string
}

// WebLoader is a web content loader
type WebLoader struct{}

// load loads the content of a page
func (w *WebLoader) load(src string) string {
    return fmt.Sprintf("I loaded this page %s", src)
}

func main() {
    webLoader := WebLoader{}
    loadContent(webLoader)
}

func loadContent(loader Loader) {
    loader.load("google.com")
}

上面的代码会给你这个编译时错误

/主要。go:20:13:不能使用webLoader(类型webLoader)作为类型Loader loadContent的参数: WebLoader没有实现Loader (Load方法有指针接收器)

要修复它,你只需要改变webLoader:= webLoader{}如下:

webLoader := &WebLoader{} 

为什么这会解决问题?因为你定义了这个函数func (w *WebLoader) Load来接受一个指针接收器。更多解释请阅读@icza和@karora的回答


延伸以上答案(谢谢你所有的回答) 我认为展示指针/非指针结构的所有方法会更本能。

这是操场的密码。 https://play.golang.org/p/jkYrqF4KyIf

总结所有的例子。

指针结构类型将包括所有非指针/指针接收器方法 非指针结构类型只包括非指针接收器方法。

对于嵌入式结构体

非指针外部结构+非指针嵌入结构=>只有非指针接收方法。 非指针外部结构+指针嵌入结构/指针外部结构+非指针嵌入结构/指针外部结构+指针嵌入结构=>所有嵌入方法