提问人:xpt 提问时间:11/27/2016 最后编辑:Jonathan Hallxpt 更新时间:2/26/2022 访问量:144090
X 不实现 Y (...方法有一个指针接收器)
X does not implement Y (... method has a pointer receiver)
问:
已经有几个关于这个的问答 “X 没有实现 Y(...方法有一个指针接收器)“的东西,但对我来说,他们似乎在谈论不同的事情,而不适用于我的具体情况。
所以,我没有把这个问题说得很具体,而是把它写得很宽泛和抽象——似乎有几种不同的情况可以使这个错误发生,有人可以总结一下吗?
即,如何避免问题,如果发生,有哪些可能性?感谢。
答:
当您尝试将具体类型赋值或传递(或转换)到接口类型时,会出现此编译时错误;而类型本身并不实现接口,只是一个指向类型的指针。
简短摘要:如果所赋值的值实现了所赋值的接口,则对接口类型的变量赋值有效。如果其方法集是接口的超集,则它会实现它。指针类型的方法集包括同时具有指针和非指针接收器的方法。非指针类型的方法集仅包括具有非指针接收器的方法。
让我们看一个例子:
type Stringer interface {
String() string
}
type MyType struct {
value string
}
func (m *MyType) String() string { return m.value }
接口类型只有一个方法:。存储在接口值中的任何值都必须具有此方法。我们还创建了一个 ,并创建了一个带有指针接收器的方法。这意味着该方法位于 type 的方法集中,但不在 .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)
但是,如果我们尝试将 type 值分配给:*MyType
Stringer
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 上尝试):MyType2
String()
MyType
*MyType2
var s Stringer
s = &m2
如果我们嵌入并仅使用非指针,我们也可以使它工作(在 Go Playground 上尝试):*MyType
MyType2
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
规范中的相关部分(来自结构类型部分):
给定一个结构类型和一个名为 的类型,提升的方法包含在结构的方法集中,如下所示:
S
T
- 如果包含匿名字段,则 和 的方法集都包含带有 receiver 的提升方法。的方法集还包括带有接收器的提升方法。
S
T
S
*S
T
*S
*T
- 如果包含匿名字段,则 和 的方法集 和 都包含带有 receiver 或 的提升方法。
S
*T
S
*S
T
*T
换句话说:如果我们嵌入一个非指针类型,非指针嵌入器的方法集只获取具有非指针接收器的方法(来自嵌入类型)。
如果我们嵌入指针类型,则非指针嵌入器的方法集将获取同时具有指针和非指针接收器(来自嵌入类型)的方法。
如果我们使用指向嵌入器的指针值,则无论嵌入类型是否为指针,指向嵌入器的指针的方法集始终获取同时具有指针和非指针接收器(来自嵌入类型)的方法。
注意:
有一个非常相似的情况,即当你有一个接口值,它包装了一个值 ,并且你尝试从中键入断言另一个接口值时,.在这种情况下,由于上述原因,断言将不成立,但我们得到一个略有不同的运行时错误:MyType
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))
评论
func (m *MyType)
func (m *MyType)
func (m MyType)
x := i.(MyType)
i.String()
(&i).String()
*T
S
S
我看到这种事情发生的另一种情况是,如果我想创建一个接口,其中某些方法将修改内部值,而其他方法则不会。
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)
}
为了简洁明了起见,假设你有一个 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
从上面的答案扩展(感谢您的所有回答)
我认为显示指针/非指针结构的所有方法会更本能。
这是 playground 代码。https://play.golang.org/p/jkYrqF4KyIf
总结所有示例。
- 指针结构类型将包括所有非指针/指针接收器方法
- 非指针结构类型将仅包括非指针接收器方法。
对于嵌入式结构体
- 非指针外部结构 + 非指针嵌入结构 => 仅非指针接收器方法。
- 非指针外部结构 + 指针嵌入结构 / 指针外部结构 + 非指针嵌入结构 / 指针外部结构 + 指针嵌入结构 => 所有嵌入方法
评论