Answers:
当您尝试将具体类型分配或传递(或转换)为接口类型时,会出现此编译时错误。并且类型本身不实现接口,仅实现指向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
赋给type的变量时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
再次出现编译时错误,因为的方法集MyType2
不包含String()
Embedded MyType
的方法,仅包含的方法集*MyType2
,因此以下工作(在Go Playground上尝试):
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类型):
给定一个结构类型
S
和一个名为的类型T
,在该方法的方法集中包括以下提升的方法:
- 如果
S
包含一个匿名字段T
,则S
和的方法集*S
都将包含带有接收器的升级方法T
。方法集*S
还包括带有接收方的提升方法*T
。- 如果
S
包含匿名字段*T
,则S
和的方法集*S
都将包含带有接收者T
或的升级方法*T
。
因此换句话说:如果我们嵌入非指针类型,则非指针嵌入器的方法集只能获取具有非指针接收器的方法(来自嵌入式类型)。
如果我们嵌入一个指针类型,则非指针嵌入器的方法集将获得具有指针和非指针接收器的方法(来自嵌入式类型)。
如果我们使用指向嵌入器的指针值,则无论嵌入类型是否为指针,指向嵌入器的指针的方法集始终会获取具有指针接收器和非指针接收器的方法(来自嵌入类型)。
注意:
有一种非常相似的情况,即当您有一个包装了值的接口值MyType
,并且尝试从中键入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))
MyType
如果您不能更改MyType
为使用值方法,则如何实际解决“注:”末尾提到的问题,该方法的接口{}包装了的值。我尝试了类似的方法,(&i).(*Stringer)
但是没有用。可能吗
x := i.(MyType)
,然后您可以调用带有指针接收器的方法,例如i.String()
,这是一种(&i).String()
成功的简写,因为变量是可寻址的。但是,更改值(指针值)的指针方法不会反映在接口值中包装的值中,这就是为什么它没有意义。
*T
不包含在的方法集中,S
因为它S
可能无法寻址(例如,函数返回值或映射索引结果),并且还因为通常仅存在/接收一个副本,并且如果允许使用其地址,则该方法使用指针接收器只能修改副本(因为您会认为原稿已修改,所以会造成混淆)。请参见以下答案以获取示例:使用反射SetString。
为了简短起见,假设您拥有此代码,并且具有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)
}
func (m *MyType)
”,或者都不是。是这样吗?我可以混合使用不同类型的“成员函数”,例如func (m *MyType)
&func (m MyType)
吗?