X不实现Y(…方法具有指针接收器)


201

关于“ X不能实现Y(...方法具有指针接收器) ”的事情已经有一些问答,但是对我来说,他们似乎在谈论不同的事情,而不适用于我的具体情况。

因此,我没有使问题变得非常具体,而是使之变得笼统和抽象-似乎有几种不同的情况可能导致此错误发生,请有人总结一下吗?

即,如何避免该问题,如果发生,可能性是什么?谢谢。

Answers:


365

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

让我们来看一个例子:

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赋给type的变量时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)

但是,如果我们尝试将type的值分配*MyTypeStringer

s = &m
fmt.Println(s)

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

something

因此,获取此编译时错误的要求:

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

解决此问题的可能性:

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

结构和嵌入

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

type MyType2 struct {
    MyType
}

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

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

再次出现编译时错误,因为的方法集MyType2不包含String()Embedded MyType的方法,仅包含的方法集*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类型):

给定一个结构类型S和一个名为的类型T,在该方法的方法集中包括以下提升的方法:

  • 如果S包含一个匿名字段T,则S和的方法集*S都将包含带有接收器的升级方法T。方法集*S还包括带有接收方的提升方法*T
  • 如果S包含匿名字段*T,则S和的方法集*S都将包含带有接收者T或的升级方法*T

因此换句话说:如果我们嵌入非指针类型,则非指针嵌入器的方法集只能获取具有非指针接收器的方法(来自嵌入式类型)。

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

如果我们使用指向嵌入器的指针值,则无论嵌入类型是否为指针,指向嵌入器的指针的方法集始终会获取具有指针接收器和非指针接收器的方法(来自嵌入类型)。

注意:

有一种非常相似的情况,即当您有一个包装了值的接口值MyType,并且尝试从中键入assert另一个接口值时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

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

m := MyType{value: "something"}

fmt.Println(Stringer(m))

感谢您的全面答复。对不起,我很晚才收到答复,很奇怪,我没有收到SO通知。我搜索的一种情况是,答案是“成员函数”应该是所有指针类型,例如“ func (m *MyType)”,或者都不是。是这样吗?我可以混合使用不同类型的“成员函数”,例如func (m *MyType)func (m MyType)吗?
xpt

3
@xpt您可以混合使用指针接收器和非指针接收器,无需完全相同。如果您有19个使用指针接收器的方法,而使用非指针接收器创建一个方法,那太奇怪了。如果您开始混合使用哪种类型的方法集,那么也很难跟踪哪些方法。此答案中的更多详细信息:Golang中的值接收器与指针接收器?
icza

MyType如果您不能更改MyType为使用值方法,则如何实际解决“注:”末尾提到的问题,该方法的接口{}包装了的值。我尝试了类似的方法,(&i).(*Stringer)但是没有用。可能吗
乔尔·埃德斯特罗姆(JoelEdström)

1
@JoelEdström是的,可以,但是没有任何意义。例如,您可以键入非指针类型的值,然后将其存储在变量中,例如x := i.(MyType),然后您可以调用带有指针接收器的方法,例如i.String(),这是一种(&i).String()成功的简写,因为变量是可寻址的。但是,更改值(指针值)的指针方法不会反映在接口值中包装的值中,这就是为什么它没有意义。
icza

1
@DeepNightTwo方法*T不包含在的方法集中,S因为它S可能无法寻址(例如,函数返回值或映射索引结果),并且还因为通常仅存在/接收一个副本,并且如果允许使用其地址,则该方法使用指针接收器只能修改副本(因为您会认为原稿已修改,所以会造成混淆)。请参见以下答案以获取示例:使用反射SetString
icza

33

为了简短起见,假设您拥有此代码,并且具有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:无法在loadContent的参数中使用webLoader(类型为WebLoader)作为Loader类型:WebLoader不实现Loader(Load方法具有指针接收器)

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

webLoader := &WebLoader{} 

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


6
到目前为止,这是最容易理解的评论。并直接解决我所面临的问题..
Maxs728

@ Maxs728同意,在回答许多Go问题时很少见。
milosmns 19-10-5

6

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

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

然后,我不会得到上述的“X未实现Y(Z法具有指针接收器)”的错误(因为它是一个编译时错误),但我有一个糟糕的一天追逐正是为什么我的测试失败.. 。

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

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)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.