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

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

所以,我没有把问题弄得非常具体,而是把它弄得广泛而抽象——似乎有几种不同的情况会导致这个错误,有人能总结一下吗?

也就是说,如何避免问题,如果发生了,有什么可能性?谢谢。

117746 次浏览

当你试图将混凝土类型赋值或传递(或转换)为接口类型时,会出现此编译时错误;并且类型本身不实现接口,只有指向类型的指针

赋值到接口类型的变量是有效的,如果被赋值的值实现了它被赋给的接口。如果它的方法集是接口的超集,它就会实现它。指针类型的方法集包括具有指针、指针和非指针接收器的方法。非指针类型的方法集包含非指针接收器的方法。

让我们来看一个例子:

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

所以得到这个编译时错误的要求是:

  • 被赋值(或传递或转换)的非点源具体类型的值
  • 被赋值给(或传递给或转换给)的接口类型
  • 具体类型具有接口所需的方法,但带有指针接收机

解决问题的可能方法:

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

结构和嵌入

当使用结构和嵌入时,通常不是"you"它实现了一个接口(提供了一个方法实现),但是是你嵌入到struct中的类型。比如下面这个例子:

type MyType2 struct {
MyType
}


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


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

同样,编译时错误,因为MyType2的方法集不包含嵌入的MyTypeString()方法,只包含*MyType2的方法集,因此以下工作(在去操场上尝试它):

var s Stringer
s = &m2

我们也可以让它工作,如果我们嵌入*MyType并且只使用非点源 MyType2(在去操场上尝试它):

type MyType2 struct {
*MyType
}


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


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: interface conversion: main.MyType is not main.Stringer:
missing method String

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

m := MyType{value: "something"}


fmt.Println(Stringer(m))

我见过的另一种情况是,如果我想创建一个接口,其中一些方法将修改内部值,而另一些则不会。

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
}

实现类型可能会有一些方法是指针接收器,一些不是,因为我有很多这些不同的东西是gettersetter,我想在测试中检查它们是否都在做预期的事情。

如果我这样做:

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

为了保持简洁,假设你有一个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")
}

上面的代码会给你这个编译时错误

< p >。/主要。go:20:13:不能使用webLoader(类型webLoader)作为类型Loader loadContent的参数: WebLoader没有实现Loader (Load方法有指针接收器)

要修复它,你只需要将webLoader := WebLoader{}更改为以下内容:

webLoader := &WebLoader{}

为什么这会解决问题?因为你定义了这个函数func (w *WebLoader) Load来接受指针接收器。更多解释请阅读@icza和@karora的回答

扩展以上的答案(谢谢你所有的答案)
我认为显示指针/非指针结构体的所有方法会更本能

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

总结所有的例子。

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

对于嵌入式结构体

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