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

huangapple go评论71阅读模式
英文:

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

问题

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

所以,我不打算提出非常具体的问题,而是提出一个广泛和抽象的问题——似乎有几种不同的情况会导致这个错误发生,有人能总结一下吗?

也就是说,如何避免这个问题,如果出现了,有哪些可能性?谢谢。

英文:

There are already several Q&As on this "X does not implement Y (... method has a pointer receiver)" thing, but to me, they seems to be talking about different things, and not applying to my specific case.

So, instead of making the question very specific, I'm making it broad and abstract -- Seems like there are several different cases that can make this error happen, can someone summary it up please?

I.e., how to avoid the problem, and if it occurs, what are the possibilities? Thx.

答案1

得分: 589

这个编译时错误是在你尝试将一个具体类型(concrete 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 类型的值赋给类型为 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)

我们会得到预期的结果:

something

解决这个问题的可能方法有:

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

在使用结构体和嵌入时,通常不是你自己实现接口(提供方法实现),而是你嵌入在你的结构体中的类型实现接口。例如:

type MyType2 struct {
    MyType
}

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

var s Stringer
s = m2 // 再次出现编译时错误

同样,由于 MyType2 的方法集不包含嵌入的 MyTypeString() 方法,所以会出现编译时错误。但是,如果我们使用指针 *MyType2,则会正常工作:

var s Stringer
s = &m2

无论我们嵌入的是 MyType 还是 *MyType,如果我们使用指针 *MyType2,它总是有效的:

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

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

给定一个结构体类型 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))

运行时恐慌(panic):

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

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

m := MyType{value: "something"}

fmt.Println(Stringer(m))
英文:

This compile-time error arises when you try to assign or pass (or convert) a concrete type to an interface type; and the type itself does not implement the interface, only a pointer to the type.

<sup>Short summary: An assignment to a variable of interface type is valid if the value being assigned implements the interface it is assigned to. It implements it if its method set is a superset of the interface. The method set of pointer types includes methods with both pointer and non-pointer receiver. The method set of non-pointer types only includes methods with non-pointer receiver.</sup>

Let's see an example:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

The Stringer interface type has one method only: String(). Any value that is stored in an interface value Stringer must have this method. We also created a MyType, and we created a method MyType.String() with pointer receiver. This means the String() method is in the method set of the *MyType type, but not in that of MyType.

When we attempt to assign a value of MyType to a variable of type Stringer, we get the error in question:

m := MyType{value: &quot;something&quot;}

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)

But everything is ok if we try to assign a value of type *MyType to Stringer:

s = &amp;m
fmt.Println(s)

And we get the expected outcome (try it on the Go Playground):

something

So the requirements to get this compile-time error:

  • A value of non-pointer concrete type being assigned (or passed or converted)
  • An interface type being assigned to (or passed to, or converted to)
  • The concrete type has the required method of the interface, but with a pointer receiver

Possibilities to resolve the issue:

  • A pointer to the value must be used, whose method set will include the method with the pointer receiver
  • Or the receiver type must be changed to non-pointer, so the method set of the non-pointer concrete type will also contain the method (and thus satisfy the interface). This may or may not be viable, as if the method has to modify the value, a non-pointer receiver is not an option.

Structs and embedding

When using structs and embedding, often it's not "you" that implement an interface (provide a method implementation), but a type you embed in your struct. Like in this example:

type MyType2 struct {
    MyType
}

m := MyType{value: &quot;something&quot;}
m2 := MyType2{MyType: m}

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

Again, compile-time error, because the method set of MyType2 does not contain the String() method of the embedded MyType, only the method set of *MyType2, so the following works (try it on the Go Playground):

var s Stringer
s = &amp;m2

We can also make it work, if we embed *MyType and using only a non-pointer MyType2 (try it on the Go Playground):

type MyType2 struct {
    *MyType
}

m := MyType{value: &quot;something&quot;}
m2 := MyType2{MyType: &amp;m}

var s Stringer
s = m2

Also, whatever we embed (either MyType or *MyType), if we use a pointer *MyType2, it will always work (try it on the Go Playground):

type MyType2 struct {
    *MyType
}

m := MyType{value: &quot;something&quot;}
m2 := MyType2{MyType: &amp;m}

var s Stringer
s = &amp;m2

Relevant section from the spec (from section Struct types):

> Given a struct type S and a type named T, promoted methods are included in the method set of the struct as follows:
>
> - If S contains an anonymous field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.
> - If S contains an anonymous field *T, the method sets of S and *S both include promoted methods with receiver T or *T.

So in other words: if we embed a non-pointer type, the method set of the non-pointer embedder only gets the methods with non-pointer receivers (from the embedded type).

If we embed a pointer type, the method set of the non-pointer embedder gets methods with both pointer and non-pointer receivers (from the embedded type).

If we use a pointer value to the embedder, regardless of whether the embedded type is pointer or not, the method set of the pointer to the embedder always gets methods with both the pointer and non-pointer receivers (from the embedded type).

Note:

There is a very similar case, namely when you have an interface value which wraps a value of MyType, and you try to type assert another interface value from it, Stringer. In this case the assertion will not hold for the reasons described above, but we get a slightly different runtime-error:

m := MyType{value: &quot;something&quot;}

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

Runtime panic (try it on the Go Playground):

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

Attempting to convert instead of type assert, we get the compile-time error we're talking about:

m := MyType{value: &quot;something&quot;}

fmt.Println(Stringer(m))

答案2

得分: 119

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

package main

import "fmt"

// Loader定义了一个内容加载器
type Loader interface {
	load(src string) string
}

// WebLoader是一个网络内容加载器
type WebLoader struct{}

// load加载页面的内容
func (w *WebLoader) load(src string) string {
	return fmt.Sprintf("我加载了这个页面:%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的答案。

英文:

To keep it short and simple, let say you have a Loader interface and a WebLoader that implements this interface.

package main

import &quot;fmt&quot;

// 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(&quot;I loaded this page %s&quot;, src)
}

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

func loadContent(loader Loader) {
	loader.load(&quot;google.com&quot;)
}

The above code will give you this compile time error

> ./main.go:20:13: cannot use webLoader (type WebLoader) as type Loader
> in argument to loadContent:
> WebLoader does not implement Loader (Load method has pointer receiver)

To fix it you only need to change webLoader := WebLoader{} to following:

webLoader := &amp;WebLoader{} 

Why this will fix the issue? Because you defined this function func (w *WebLoader) Load to accept a pointer receiver. For more explanation please read @icza and @karora answers

答案3

得分: 9

我见过类似情况发生的另一个案例是,当我想创建一个接口,其中一些方法会修改内部值,而其他方法则不会。

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 {
    oldVal := m.a
    m.a = newVal
    return oldVal
}

因此,实现类型可能会有一些方法是指针接收器,而其他方法则不是。由于我有各种各样的GetterSetter类型,我希望在测试中检查它们是否都按预期工作。

如果我像这样做:

myTypeInstance := MyType{ 7 }
... 一些其他操作的代码 ...
var f interface{} = myTypeInstance
_, ok := f.(GetterSetter)
if !ok {
    t.Fail()
}

那么我将不会得到前面提到的"X does not implement Y (Z method has pointer receiver)"错误(因为这是一个编译时错误),但我会因为追踪测试失败的原因而感到糟糕。

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

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)
}
英文:

Another case when I have seen this kind of thing happening is if I want to create an interface where some methods will modify an internal value and others will not.

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

Something that then implements this interface could be like:

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
}

So the implementing type will likely have some methods which are pointer receivers and some which are not and since I have quite a variety of these various things that are GetterSetters I'd like to check in my tests that they are all doing the expected.

If I were to do something like this:

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

Then I won't get the aforementioned "X does not implement Y (Z method has pointer receiver)" error (since it is a compile-time error) but I will have a bad day chasing down exactly why my test is failing...

Instead I have to make sure I do the type check using a pointer, such as:

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

Or:

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

Then all is happy with the tests!

But wait! In my code, perhaps I have methods which accept a GetterSetter somewhere:

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

If I call these methods from inside another type method, this will generate the error:

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

Either of the following calls will work:

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

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

答案4

得分: 3

从上面的回答中延伸(感谢你的所有回答)
我认为展示指针/非指针结构的所有方法会更直观。

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

总结所有的例子。

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

对于嵌入结构

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

Extend from above answers (Thanks for all of your answers)
I think it would be more instinctive to show all the methods of pointer / non pointer struct.

Here is the playground code.
https://play.golang.org/p/jkYrqF4KyIf

To summarize all the example.

  1. Pointer struct type would include all non pointer / pointer receiver methods
  2. Non pointer struct type would only include non pointer receiver methods.

For embedded struct

  1. non pointer outer struct + non pointer embedded struct => only non pointer receiver methods.
  2. non pointer outer struct + pointer embedded struct / pointer outer struct + non pointer embedded struct / pointer outer struct + pointer embedded struct => all embedded methods

huangapple
  • 本文由 发表于 2016年11月27日 05:24:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/40823315.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定