当您尝试分配或传递(或转换)一个concrete类型为接口类型;并且类型本身并没有实现接口,只是一个指向类型的指针.
Short summary: An assignment https://golang.org/ref/spec#Assignments to a variable of interface type is valid if the value being assigned implements the interface it is assigned to. It implements it if its method set https://golang.org/ref/spec#Method_sets is a superset of the interface. The method set of pointer types includes methods with both pointer and non-pointer receiver. The method set of non-pointer types only includes methods with non-pointer receiver.
让我们看一个例子:
type Stringer interface {
String() string
}
type MyType struct {
value string
}
func (m *MyType) String() string { return m.value }
The Stringer
接口类型只有一种方法:String()
。存储在接口值中的任何值Stringer
一定有这个方法。我们还创建了一个MyType
,我们创建了一个方法MyType.String()
with pointer接收者。这意味着String()
方法是在方法集 https://golang.org/ref/spec#Method_sets of the *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
to Stringer
:
s = &m
fmt.Println(s)
我们得到了预期的结果(尝试一下去游乐场 https://play.golang.org/p/TSaT30PRD0):
something
因此,获得此编译时错误的要求:
- 值为非指针被分配(或传递或转换)的具体类型
- 被分配(或传递或转换)的接口类型
- 具体类型具有接口所需的方法,但带有一个指针接收器
解决该问题的可能性:
- 必须使用指向该值的指针,其方法集将包括带有指针接收者的方法
- 或者必须将接收器类型更改为非指针,因此非指针具体类型的方法集也将包含该方法(从而满足接口)。这可能可行,也可能不可行,就像该方法必须修改值一样,非指针接收器不是一个选项。
结构和嵌入
使用时结构和嵌入 https://golang.org/ref/spec#Struct_types,通常不是“您”实现接口(提供方法实现),而是您嵌入到您的struct
。就像这个例子一样:
type MyType2 struct {
MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: m}
var s Stringer
s = m2 // Compile-time error again
再次,编译时错误,因为方法集MyType2
不包含String()
嵌入方法MyType
,只有方法集*MyType2
,所以以下工作(在去游乐场 https://play.golang.org/p/3MuyevNpL8):
var s Stringer
s = &m2
如果我们嵌入,我们也可以让它工作*MyType
并且只使用一个非指针 MyType2
(尝试一下去游乐场 https://play.golang.org/p/BMG5EctMVa):
type MyType2 struct {
*MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: &m}
var s Stringer
s = m2
另外,无论我们嵌入什么(要么MyType
or *MyType
),如果我们使用指针*MyType2
,它总是有效的(尝试一下去游乐场 https://play.golang.org/p/3MuyevNpL8):
type MyType2 struct {
*MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: &m}
var s Stringer
s = &m2
规范中的相关部分(来自部分结构类型 https://golang.org/ref/spec#Struct_types):
给定一个结构体类型S
和一个名为T
,提升的方法包含在结构体的方法集中,如下所示:
- If
S
包含匿名字段T
,方法集S
and *S
两者都包括带有接收器的升级方法T
。方法集为*S
还包括带有接收器的升级方法*T
.
- If
S
包含匿名字段*T
,方法集S
and *S
两者都包括带有接收器的升级方法T
or *T
.
换句话说:如果我们嵌入一个非指针类型,非指针嵌入器的方法集只能获取具有非指针接收器的方法(来自嵌入类型)。
如果我们嵌入指针类型,非指针嵌入器的方法集将获取具有指针和非指针接收器的方法(来自嵌入类型)。
如果我们使用指向嵌入器的指针值,则无论嵌入类型是否是指针,指向嵌入器的指针的方法集始终都会获取具有指针和非指针接收器的方法(来自嵌入类型)。
Note:
有一个非常相似的情况,即当您有一个包装值的接口值时MyType
,然后你尝试类型断言 https://golang.org/ref/spec#Type_assertions另一个接口值,Stringer
。在这种情况下,由于上述原因,断言将不成立,但我们会得到略有不同的运行时错误:
m := MyType{value: "something"}
var i interface{} = m
fmt.Println(i.(Stringer))
运行时恐慌(尝试一下去游乐场 https://play.golang.org/p/Mos344EWbH):
panic: interface conversion: main.MyType is not main.Stringer:
missing method String
尝试转换而不是类型断言,我们得到了我们正在讨论的编译时错误:
m := MyType{value: "something"}
fmt.Println(Stringer(m))