15

I am newbie gopher and trying to get my head around the pointer receivers and interfaces.

type Foo interface {
    foo()
}
type Bar struct {}
func (b *Bar) foo() {}

based on the above definitions..

--- Allowed ---------
b := Bar{}
b.foo()

--- Not allowed ----- 

var foo Foo = Bar{}

Get compiler error: cannot use Bar literal (type Bar) as type Foo in assignment: Bar does not implement Foo (foo method has pointer receiver)

I understand that compiler is doing some pointer conversion and de-referencing on our behalf in the first scenario. Why doesn't it do the same thing in the second scenario ?

4

3 回答 3

20

简短的回答var foo Foo = Bar{}不起作用,因为存储在接口中的具体值是不可寻址的。

更长的版本

请阅读https://github.com/golang/go/wiki/MethodSets

在已经是指针或可以获取其地址的任何东西上调用指针值方法是合法的。对任何值或可以取消引用的值调用值方法是合法的。

关于上面的解释,你的代码

b := Bar{}
b.foo()

有效,因为b是可寻址的。

存储在接口中的具体值是不可寻址的。因此,当您在接口上调用方法时,它必须具有相同的接收器类型,或者必须可以直接从具体类型中辨别出来:指针和值接收器方法可以分别使用指针和值调用,正如您所期望的那样. 可以使用指针值调用值接收器方法,因为它们可以首先被取消引用。然而,指针接收器方法不能用值调用,因为存储在接口中的值没有地址。将值分配给接口时,编译器确保所有可能的接口方法实际上都可以在该值上调用,因此尝试进行不正确的分配将在编译时失败。

根据上面的解释,存储在接口中的具体值是不可寻址的,因此代码,

var foo Foo = Bar{}

将不起作用,因为在这种情况下Bar{},存储在接口中的具体值是不可寻址的。

于 2017-08-12T19:11:11.383 回答
4

解释在于,在处理具体结构本身时,它有适当的信息来自动处理。您可以在此处的导览中阅读:

Go 自动处理方法调用的值和指针之间的转换。

但是当您处理一个interface{}类型时,它对变量中实际包含的内容的信息较少。它只知道有一种foo()方法。但是这里有一个微妙之处需要额外的解释,所以这里是一个例子。

https://play.golang.org/p/Y0fJcAISw1

type Foo interface {
    foo()
}
type Bar struct {}
func (b *Bar) foo() {}

type Baz struct {}
func (b Baz) foo() {}

func main() {
    b := Bar{}
    b.foo()

    var v Foo = &Bar{}
    // v = Bar{} // fails
    v.foo()

    v = Baz{}
    v.foo()
    v = &Baz{} // works too
    v.foo()
}

请注意,&Baz{}即使它有一个值接收器,它也可以工作,但反之则不行。原因是 a*Baz正好指向one Baz,两者都存在(指针和值),所以值很容易获得。当你尝试 dov = Bar{}时,值存在,但指针不存在,Go 不会自动为interface{}值创建一个。

这一切都在这篇博文的指针和接口标题下详细解释

于 2017-08-12T18:42:45.950 回答
3

您的问题有一半取决于您的价值是否可寻址:

对于x类型的操作数T,地址操作会&x生成指向 的类型的*T指针x。操作数必须是可寻址的,即:

  • 一个变量,
  • 指针间接,或
  • 切片索引操作;或者
  • 可寻址结构操作数的字段选择器;或者
  • 可寻址数组的数组索引操作。

作为可寻址性要求的一个例外,它x也可以是(可能带括号的)复合文字。

地址运算符

Bar{}是一个复合文字,因此它是不可寻址的。您可以键入&Bar{}以创建 type 的对象*Bar,但这被列为“可寻址性要求的例外”,强化了Bar{}本身不可寻址的想法。

尽管出于充分的理由需要指针接收器,但b类型变量Bar可以调用:b.foo()Bar.foo()

x.m()如果(的类型)的方法集x 包含m并且参数列表可以分配给 的参数列表,则方法调用是有效的mIfx是可寻址的并且&x's 方法集包含m, x.m()是的简写(&x).m()

通话

但是,这并不意味着Bar.foo()在方法集中b。这是因为bhas type BarwhileBar.foo()接收 type 的值*Bar

一个类型可能有一个与之关联的方法集。接口类型的方法集就是它的接口。任何其他类型的方法集T 由声明为接收者类型的所有方法组成T。对应指针类型的方法*T集是所有用receiver *Tor声明的方法的T集合(即它还包含 的方法集T)。

— 来自方法集

由于 的方法集bFoo接口不同,您不能使用 var foo Foo = b,尽管已被编译器b.foo()转换为。(&b).foo()否则,var foo Foo = Bar{}会工作。但是,您可以使用以下任一方法,因为Bar.foo()收到 a *Bar

var foo Foo = &b
var foo Foo = &Bar{}
于 2017-08-12T19:29:12.433 回答