接口和指针接收器

huangapple go评论101阅读模式
英文:

Interfaces and pointer receivers

问题

我是一个新手gopher,正在努力理解指针接收器和接口。

根据上述定义...

--- 允许的 ---
b := Bar{}
b.foo()

--- 不允许的 ---
var foo Foo = Bar{}

得到编译器错误:
无法将Bar字面量(类型为Bar)用作分配中的类型Foo:
Bar未实现Foo(foo方法具有指针接收器)

我理解编译器在第一种情况下会自动进行一些指针转换和解引用操作。为什么在第二种情况下它不做同样的操作呢?

英文:

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

得分: 24

短答案var foo Foo = Bar{}无法工作,因为接口中存储的具体值是不可寻址的。

更详细的解释

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

> 可以在任何已经是指针或其地址可以被获取的内容上调用指针值方法是合法的。可以在任何是值或其值可以被解引用的内容上调用值方法是合法的。

根据上述解释,你的代码

b := Bar{}
b.foo()

之所以能够工作,是因为b是可寻址的。

> 存储在接口中的具体值是不可寻址的。因此,当你在接口上调用方法时,它要么具有相同的接收器类型,要么可以直接从具体类型中区分出来:指针接收器和值接收器的方法可以分别使用指针和值进行调用,就像你期望的那样。值接收器的方法可以使用指针值进行调用,因为它们可以首先进行解引用。然而,指针接收器的方法不能使用值进行调用,因为接口中存储的值没有地址。当将一个值赋给接口时,编译器会确保所有可能的接口方法实际上都可以在该值上调用,因此尝试进行不正确的赋值将在编译时失败。

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

var foo Foo = Bar{}

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

英文:

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

得分: 7

解释在于当处理具体的结构体本身时,它具有处理这一点的正确信息。你可以在这里的教程中阅读到:

> 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{} // 失败
	v.foo()
	
	v = Baz{}
	v.foo()
	v = &Baz{} // 也可以工作
	v.foo()
}

请注意,&Baz{}可以工作,即使它具有值接收器,但反之则不行。原因在于*Baz指向完全一个Baz,两者都存在(指针和值),因此很容易获取值。当你尝试执行v = Bar{}时,值存在,但指针不存在,Go不会自动为interface{}值创建指针。

这一切都在这篇博文的指针和接口部分中详细解释。

英文:

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

1: https://gobyexample.com/methods "here"
2: http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go

答案3

得分: 4

你的问题的一半取决于你的值是否可寻址:

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

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

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

Bar{}是一个复合字面量,因此它本身不可寻址。你可以输入&Bar{}来创建一个类型为*Bar的对象,但这被列为“对可寻址要求的例外”,强调了Bar{}本身不可寻址的观点。

类型为Bar的变量b可以调用b.foo(),尽管Bar.foo()要求一个指针接收器有一个很好的理由:

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

然而,这并不意味着Bar.foo()b的方法集中。这是因为b的类型是Bar,而Bar.foo()接收一个类型为*Bar的值:

一个类型可以与其关联一个方法集。接口类型的方法集是其接口。任何其他类型T的方法集由所有声明的接收器类型为T的方法组成。相应指针类型*T的方法集是所有声明的接收器为*TT的方法的集合(也就是说,它还包含T的方法集)。

因为bFoo接口的方法集不同,所以你不能使用var foo Foo = b,尽管编译器将b.foo()转换为(&b).foo()。否则,var foo Foo = Bar{}将起作用。但是,你可以使用以下任一方式,因为Bar.foo()接收一个*Bar

var foo Foo = &b
var foo Foo = &Bar{}
英文:

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{}

huangapple
  • 本文由 发表于 2017年8月13日 00:32:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/45652560.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定