Interfaces and pointer receivers

我们两清 提交于 2019-12-22 05:23:36

问题


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 ?


回答1:


Short answer var foo Foo = Bar{} is not working because the concrete value stored in an interface is not addressable.

Longer Version

Please read https://github.com/golang/go/wiki/MethodSets

It is legal to call a pointer-valued method on anything that is already a pointer or whose address can be taken. It is legal to call a value method on anything which is a value or whose value can be dereferenced.

With respect to the above explanation, your code

b := Bar{}
b.foo()

works because b is addressable.

The concrete value stored in an interface is not addressable. Therefore, when you call a method on an interface, it must either have an identical receiver type or it must be directly discernible from the concrete type: pointer- and value-receiver methods can be called with pointers and values respectively, as you would expect. Value-receiver methods can be called with pointer values because they can be dereferenced first. Pointer-receiver methods cannot be called with values, however, because the value stored inside an interface has no address. When assigning a value to an interface, the compiler ensures that all possible interface methods can actually be called on that value, and thus trying to make an improper assignment will fail on compilation.

According to the above explanation the concrete value stored in an interface is not addressable and hence the code,

var foo Foo = Bar{}

will not work because the concrete value stored in an interface, in this case Bar{}, is not addressable.




回答2:


The explanation lies in the fact that when dealing with the concrete struct itself, it has the proper information to handle this automatically. You can read in the tour here that:

Go automatically handles conversion between values and pointers for method calls.

But when you are dealing with an interface{} type, it has less information on what is actually contained in the variable. It just knows there is a foo() method. But there is a subtlety here that requires extra explanation so here is an example.

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()
}

Notice that &Baz{} works even though it has a value receiver, but not the reverse. The reason being that a *Baz points to exactly one Baz, both of which exist (the pointer and the value), so the value is easily obtained. When you try to do v = Bar{}, the value exists, but the pointer does not, and Go will not automatically create one for an interface{} value.

This is all explained in detail under the Pointers and interfaces heading in this blog post




回答3:


Half of your question depends on whether your value is addressable or not:

For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either:

  • a variable,
  • pointer indirection, or
  • slice indexing operation; or
  • a field selector of an addressable struct operand; or
  • an array indexing operation of an addressable array.

As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal.

— Address operators

Bar{} is a composite literal, thus it is not addressable. You may type &Bar{} to create an object of type *Bar, but that is listed as "an exception to the addressability requirement", reinforcing the idea that Bar{} is not addressable in itself.

Variable b of type Bar can invoke b.foo() despite Bar.foo() requiring a pointer receiver for a good reason:

A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()

— Calls

However, that does not mean Bar.foo() is in the method set of b. This is because b has type Bar while Bar.foo() receives a value of type *Bar:

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

— from Method sets

Because the method sets of b and the Foo interface differ, you cannot use var foo Foo = b, despite b.foo() being converted to (&b).foo() by the compiler. Otherwise, var foo Foo = Bar{} would work. You may, however, use either of the following since Bar.foo() receives a *Bar:

var foo Foo = &b
var foo Foo = &Bar{}


来源:https://stackoverflow.com/questions/45652560/interfaces-and-pointer-receivers

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!