ChatGPT解决这个技术问题 Extra ChatGPT

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

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

因此,我没有将问题变得非常具体,而是将其变得广泛和抽象——似乎有几种不同的情况会导致此错误发生,有人可以总结一下吗?

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


i
icza

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

简短摘要:如果分配的值实现了分配给它的接口,则接口类型变量的 assignment 是有效的。如果它的 method set 是接口的超集,它就会实现它。指针类型的方法集包括具有both 指针和非指针接收器的方法。非指针类型的方法集包括具有非指针接收器的方法。

让我们看一个例子:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Stringer 接口类型只有一种方法:String()。存储在接口值 Stringer 中的任何值都必须具有此方法。我们还创建了一个 MyType,并创建了一个带有 pointer 接收器的方法 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的方法集不包含嵌入MyTypeString()方法,只有*MyType2的方法集,所以下面的工作(在{1 }):

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 types 部分):

给定一个结构类型 S 和一个名为 T 的类型,提升的方法包含在结构的方法集中,如下所示: 如果 S 包含匿名字段 T,则 S 和 *S 的方法集都包含具有接收者 T 的提升方法。 *S 的方法集还包括带有接收器 *T 的提升方法。如果 S 包含匿名字段 *T,则 S 和 *S 的方法集都包含带有接收者 T 或 *T 的提升方法。

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

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

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

笔记:

有一个非常相似的情况,即当您有一个包含 MyType 值的接口值时,您尝试从中type 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 通知。我搜索了一个案例,答案是“成员函数”应该是all 指针类型,例如“func (m *MyType)”,或者none。是这样吗?我可以混合不同类型的“成员函数”,例如 func (m *MyType)func (m MyType)
@xpt您可以混合使用指针和非指针接收器,不需要全部相同。如果您有 19 种带有指针接收器的方法,而您使用非指针接收器制作了一种,那就太奇怪了。如果您开始混合它们,这也使得跟踪哪些方法是哪些类型的方法集的一部分变得更加困难。此答案中的更多详细信息:Value receiver vs. Pointer receiver in Golang?
@JoelEdström 是的,这是可能的,但这没什么意义。例如,您可以对非指针类型的值进行类型断言并将其存储在变量中,例如 x := i.(MyType),然后您可以调用带有指针接收器的方法,例如 i.String(),它是 { 3} 成功是因为变量是可寻址的。但是改变值的指针方法(被指向的值)并不会体现在接口值包裹的值上,所以意义不大。
故事的寓意:即使指针类型和非指针类型使用相同的底层类型,它们也不是一回事。因此,请相应地对待它们。这就是为什么哪个实现一种方法来满足接口很重要的原因!
@DeepNightTwo *T 的方法不包含在 S 的方法集中,因为 S 可能不可寻址(例如函数返回值或地图索引的结果),并且还因为通常只存在/接收副本,并且如果允许获取其地址,则带有指针接收器的方法只能修改副本(您会假设原始文件已被修改,因此会感到困惑)。有关示例,请参见此答案:Using reflection SetString
S
Saman

为了简短起见,假设您有一个 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 答案


到目前为止,这是最容易理解的评论。并直接解决了我面临的问题..
@Maxs728 同意,在许多 Go 问题的答案中并不常见。
切中要害,易于理解。我在截止日期中间需要什么
很好的答案,简单是成功之母
耶稣同意这个答案❤️
k
karora

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

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

鄭元傑

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

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

总结所有的例子。

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

对于嵌入式结构

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅