X没有实现Y(...方法有一个指针接收器)

xpt*_*xpt 157 methods pointers interface go

关于这个" X没有实现Y(...方法有一个指针接收器) "的事情,已经有几个问答,但对我来说,他们似乎在谈论不同的事情,而不是适用于我的具体案例.

因此,我没有将问题变得非常具体,而是将其广泛和抽象化 - 似乎有几个不同的案例可以使这个错误发生,有人可以总结一下吗?

即,如何避免这个问题,如果发生,有什么可能性?谢谢.

icz*_*cza 294

当您尝试将具体类型分配或传递(或转换)为接口类型时,会出现此编译时错误; 并且类型本身不实现接口,只有指向该类型指针.

我们来看一个例子:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

func (m *MyType) String() string { return m.value }
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

但是如果我们尝试将类型的值赋值*MyTypeStringer:

s = &m
fmt.Println(s)
Run Code Online (Sandbox Code Playgroud)

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

something
Run Code Online (Sandbox Code Playgroud)

因此需要获得此编译时错误:

  • 指定(或传递或转换)非指针具体类型的值
  • 分配给(或传递给或转换为)的接口类型
  • 具体类型具有接口所需的方法,但具有指针接收器

解决问题的可能性:

  • 必须使用指向该值的指针,其方法集将包括带有指针接收器的方法
  • 或者接收器类型必须更改为非指针,因此非指针具体类型的方法集也将包含该方法(从而满足接口).这可能是也可能不可行,就好像该方法必须修改该值一样,非指针接收器不是一种选择.

结构和嵌入

使用结构体和嵌入时,通常不是"你"实现接口(提供方法实现),而是嵌入你的类型struct.就像在这个例子中:

type MyType2 struct {
    MyType
}

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

var s Stringer
s = m2 // Compile-time error again
Run Code Online (Sandbox Code Playgroud)

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

var s Stringer
s = &m2
Run Code Online (Sandbox Code Playgroud)

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

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2
Run Code Online (Sandbox Code Playgroud)

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

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2
Run Code Online (Sandbox Code Playgroud)

规范中的相关部分(来自结构类型部分):

给定结构类型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))
Run Code Online (Sandbox Code Playgroud)

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

panic: interface conversion: main.MyType is not main.Stringer:
    missing method String
Run Code Online (Sandbox Code Playgroud)

尝试转换而不是类型断言,我们得到了我们正在讨论的编译时错误:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
Run Code Online (Sandbox Code Playgroud)

  • @xpt你可以混合指针和非指针接收器,这不是要求完全相同的要求.如果您有19个带指针接收器的方法并且使用非指针接收器制作一个方法,那就太奇怪了.如果你开始混合它们,它也更难跟踪哪些方法属于哪种类型的方法集.这个答案的更多细节:[Golang中的值接收器与指针接收器?](http://stackoverflow.com/questions/27775376/value-receiver-vs-pointer-receiver-in-golang/27775558#27775558) (3认同)

Sam*_*igh 21

为了简短起见,假设你有这个代码,你有一个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")
}
Run Code Online (Sandbox Code Playgroud)

所以这段代码会给你这个编译时错误

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

所以你只需要改变webLoader := WebLoader{}如下:

webLoader := &WebLoader{} 
Run Code Online (Sandbox Code Playgroud)

那么为什么它会修复因为你定义这个函数func (w *WebLoader) Load来接受一个指针接收器.有关更多说明,请阅读@icza和@karora答案

  • 到目前为止,这是最容易理解的评论。并直接解决了我面临的问题。 (5认同)

kar*_*ora 6

看到这种情况发生的另一种情况是,如果我想创建一个接口,其中某些方法将修改内部值,而其他方法则不会。

type GetterSetter interface {
   GetVal() int
   SetVal(x int) int
}
Run Code Online (Sandbox Code Playgroud)

然后实现此接口的内容可能是:

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
}
Run Code Online (Sandbox Code Playgroud)

因此,实现类型可能会有一些方法,这些方法是指针接收器,而有些则不是,并且由于我有各种各样的GetterSetters方法,我想在测试中检查它们是否都按预期执行。

如果我要做这样的事情:

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

然后,我不会收到前面提到的“ X不能实现Y(Z方法具有指针接收器)”错误(因为它是编译时错误),但是我很难追查测试失败的确切原因。 。

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

var f interface{} = new(&MyTypeA)
 ...
Run Code Online (Sandbox Code Playgroud)

要么:

myTypeInstance := MyType{ 7 }
var f interface{} = &myTypeInstance
...
Run Code Online (Sandbox Code Playgroud)

然后所有人都对测试感到满意!

可是等等!在我的代码中,也许我有一些在某个地方接受GetterSetter的方法:

func SomeStuff(g GetterSetter, x int) int {
    if x > 10 {
        return g.GetVal() + 1
    }
    return g.GetVal()
}
Run Code Online (Sandbox Code Playgroud)

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

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}
Run Code Online (Sandbox Code Playgroud)

以下任何一个呼叫都将起作用:

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

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
Run Code Online (Sandbox Code Playgroud)