当您尝试将具体类型分配或传递(或转换)为接口类型时,会出现此编译时错误;并且类型本身没有实现接口,只是一个指向类型的指针。
简短摘要:如果分配的值实现了分配给它的接口,则接口类型变量的 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
的方法集不包含嵌入MyType
的String()
方法,只有*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))
为了简短起见,假设您有一个 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 答案
我看到这种事情发生的另一种情况是,如果我想创建一个接口,其中一些方法将修改内部值,而其他方法则不会。
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
总结所有的例子。
指针结构类型将包括所有非指针/指针接收器方法非指针结构类型将仅包括非指针接收器方法。
对于嵌入式结构
非指针外部结构+非指针嵌入结构=>只有非指针接收器方法。非指针外部结构+指针嵌入结构/指针外部结构+非指针嵌入结构/指针外部结构+指针嵌入结构=>所有嵌入方法
func (m *MyType)
”,或者none。是这样吗?我可以混合不同类型的“成员函数”,例如func (m *MyType)
和func (m MyType)
?x := i.(MyType)
,然后您可以调用带有指针接收器的方法,例如i.String()
,它是 { 3} 成功是因为变量是可寻址的。但是改变值的指针方法(被指向的值)并不会体现在接口值包裹的值上,所以意义不大。*T
的方法不包含在S
的方法集中,因为S
可能不可寻址(例如函数返回值或地图索引的结果),并且还因为通常只存在/接收副本,并且如果允许获取其地址,则带有指针接收器的方法只能修改副本(您会假设原始文件已被修改,因此会感到困惑)。有关示例,请参见此答案:Using reflection SetString。