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

X does not implement Y (... method has a pointer receiver)

提问人:xpt 提问时间:11/27/2016 最后编辑:Jonathan Hallxpt 更新时间:2/26/2022 访问量:144090

问:

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

所以,我没有把这个问题说得很具体,而是把它写得很宽泛和抽象——似乎有几种不同的情况可以使这个错误发生,有人可以总结一下吗?

即,如何避免问题,如果发生,有哪些可能性?感谢。

指针 Go 方法 接口

评论


答:

602赞 icza 11/27/2016 #1

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

简短摘要:如果所赋值的值实现了所赋值的接口,则对接口类型的变量值有效。如果其方法集是接口的超集,则它会实现它。指针类型的方法集包括同时具有指针和非指针接收器的方法。非指针类型的方法集包括具有非指针接收器的方法。

让我们看一个例子:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

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

当我们尝试将 的值分配给 类型的变量时,我们得到了有问题的错误:MyTypeStringer

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

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

var s Stringer
s = &m2

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

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

此外,无论我们嵌入什么(要么或),如果我们使用指针,它总是可以工作的(在 Go Playground 上尝试):MyType*MyType*MyType2

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

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

给定一个结构类型和一个名为 的类型,提升的方法包含在结构的方法集中,如下所示:ST

  • 如果包含匿名字段,则 和 的方法集都包含带有 receiver 的提升方法。的方法集还包括带有接收器的提升方法。STS*ST*S*T
  • 如果包含匿名字段,则 和 的方法集 和 都包含带有 receiver 或 的提升方法。S*TS*ST*T

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

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

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

注意:

有一个非常相似的情况,即当你有一个接口值,它包装了一个值 ,并且你尝试从中键入断言另一个接口值时,.在这种情况下,由于上述原因,断言将不成立,但我们得到一个略有不同的运行时错误:MyTypeStringer

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

评论

0赞 xpt 12/1/2016
感谢您提供非常全面的回答。很抱歉回复晚了,奇怪的是我没有收到 SO 通知。我搜索过的一个案例,答案是“成员函数”应该是所有指针类型,例如“”,或者没有。是这样吗?我可以混合不同类型的“成员函数”吗,例如 & ?func (m *MyType)func (m *MyType)func (m MyType)
3赞 icza 12/1/2016
@xpt 您可以混合使用指针接收器和非指针接收器,但不需要完全相同。如果你有 19 种带有指针接收器的方法,而你用非指针接收器制作一个方法,那就太奇怪了。如果您开始混合它们,它还会更难跟踪哪些方法是哪些类型的方法集的一部分。此答案中的更多详细信息:Golang 中的值接收器与指针接收器?
1赞 icza 7/18/2017
@JoelEdström 是的,这是可能的,但意义不大。例如,您可以对非指针类型的值进行类型置位并将其存储在变量中,例如 ,然后您可以调用带有指针接收器的方法,例如 ,这是一个简写,因为变量是可寻址的,因此成功。但是指针方法更改值(指向值)不会反映在接口值中包装的值中,这就是为什么它没有多大意义的原因。x := i.(MyType)i.String()(&i).String()
1赞 Tarik 9/20/2017
故事的寓意:指针类型和非指针类型不是一回事,即使它们使用相同的基础类型也是如此。因此,请相应地对待它们。这就是为什么哪一个实现一个方法来满足接口很重要的原因!
1赞 icza 9/22/2017
@DeepNightTwo 的方法不包含在方法集中,因为可能无法寻址(例如函数返回值或映射索引的结果),并且还因为通常只存在/接收副本,并且如果允许获取其地址,则带有指针接收器的方法只能修改副本(混淆,因为您会认为原始副本已被修改)。有关示例,请参阅此答案:使用反射 SetString*TSS
10赞 karora 2/14/2018 #2

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

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)
}
125赞 Saman 6/21/2018 #3

为了简洁明了起见,假设你有一个 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{} 

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

3赞 鄭元傑 3/19/2021 #4

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

这是 playground 代码。https://play.golang.org/p/jkYrqF4KyIf

总结所有示例。

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

对于嵌入式结构体

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