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

时间:2016-11-26 21:24:32

标签: pointers go methods interface

已经有几个Q& As就这个“ X没有实现Y(...方法有一个指针接收器)”的东西,但对我来说,他们似乎在谈论不同的事情,而不适用于我的具体案例。

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

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

4 个答案:

答案 0 :(得分:239)

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

让我们看一个例子:

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类型的method set,但不属于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,那么一切正常:

s = &m
fmt.Println(s)

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

something

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

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

解决问题的可能性:

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

结构和嵌入

使用structs and embedding时,通常不会"你"实现接口(提供方法实现),但是您嵌入struct的类型。就像在这个例子中一样:

type MyType2 struct {
    MyType
}

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

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

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

*MyType2

如果我们嵌入var s Stringer s = &m2 并仅使用非指针 *MyType(在Go Playground上尝试),我们也可以使其有效:< / p>

MyType2

此外,无论我们嵌入了什么(type MyType2 struct { *MyType } m := MyType{value: "something"} m2 := MyType2{MyType: &m} var s Stringer s = m2 MyType),如果我们使用指针*MyType,它将始终有效(在Go Playground上尝试):

*MyType2

规范中的相关部分(来自Struct types部分):

  

给定结构类型type MyType2 struct { *MyType } m := MyType{value: "something"} m2 := MyType2{MyType: &m} var s Stringer s = &m2 和名为S的类型,提升的方法包含在结构的方法集中,如下所示:

     
      
  • 如果T包含匿名字段S,则TS的方法集都包含带有接收方*S的提升方法。方法集T还包括使用接收方*S的推广方法。
  •   
  • 如果*T包含匿名字段S,则*TS的方法集都包含带有接收方*ST的提升方法}。
  •   

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

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

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

注意:

有一个非常类似的情况,即当你有一个包含*T值的接口值,并尝试type assert来自它的另一个接口值MyType时。在这种情况下,由于上述原因断言不会成立,但我们得到的运行时错误略有不同:

Stringer

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

m := MyType{value: "something"}

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

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

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

答案 1 :(得分:12)

为了简短起见,假设您拥有此代码,并且具有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")
}

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

  

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

因此,您唯一需要做的就是将webLoader := WebLoader{}更改为以下内容:

webLoader := &WebLoader{} 

所以为什么会解决,因为您定义了此函数func (w *WebLoader) Load来接受指针接收器。有关更多说明,请阅读@icza和@karora答案

答案 2 :(得分:3)

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

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
}

所以实现类型可能有一些方法是指针接收器,有些方法不是,因为我有各种各样的GetterSetters这些东西,我想在我的测试中检查它们都是做到了预期。

如果我要做这样的事情:

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

然后我没有得到上述&#34; X没有实现Y(Z方法有指针接收器)&#34;错误(因为它是一个编译时错误),但我有一个糟糕的一天追逐我的测试失败的确切原因......

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

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

或者:

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

答案 3 :(得分:1)

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

这是游乐场代码。 https://play.golang.org/p/jkYrqF4KyIf

总结所有的例子。

  1. 指针结构类型将包括所有非指针/指针接收器方法
  2. 非指针结构类型将仅包括非指针接收器方法。

对于嵌入式结构

  1. 非指针外部结构 + 非指针嵌入结构 => 仅非指针接收器方法。
  2. 非指针外结构+指针嵌入结构/指针外结构+非指针嵌入结构/指针外结构+指针嵌入结构=>所有嵌入方法