为什么隐式非指针方法不能满足接口要求?

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

Why implicit non-pointer methods not satisfy interface?

问题

为什么隐式的非指针方法不能满足接口要求呢?

隐式的非指针方法不能满足接口要求是因为在Go语言中,接口类型的方法集只包含对应方法的值接收者方法。换句话说,只有使用指针作为接收者声明的方法才能被隐式地添加到接口类型的方法集中。这是因为指针方法可以操作指针所指向的实际对象,而非指针方法只能操作方法接收者的副本。因此,如果一个类型只有非指针方法,它将无法满足接口的方法要求,因为接口类型的方法集中不包含这些非指针方法。

英文:

> Assuming we have an understanding that,

> For explicit method definition for type X, GO compiler implicitly defines the same method for type *X and vice versa, if I declare,

> func (c Cat) foo(){
//do stuff_
}

> and declare,

> func (c *Cat) foo(){
// do stuff_
}

> then GO compiler gives error,

> Compile error: method re-declared

> which indicates that, pointer method is implicitly defined and vice versa


In the below code,

package main

type X interface{
  foo();
  bar();
}

type Cat struct{
  
}

func (c Cat) foo(){
  // do stuff_
}

func (c *Cat) bar(){
  // do stuff_
}

func main() {
  var c Cat
  var p *Cat
  var x X
  
  x = p // OK; *Cat has explicit method bar() and implicit method foo()
  x = c	//compile error: Cat has explicit method foo() and implicit method bar()

}

GO compiler gives error,

cannot use c (type Cat) as type X in assignment:
	Cat does not implement X (bar method has pointer receiver)

at x = c, because, implicit pointer methods satisfy interfaces, but implicit non-pointer methods do not.

Question:

Why implicit non-pointer methods do not satisfy interfaces?

答案1

得分: 6

让我们来看一下语言规范:

  • 一个类型可以有一个与之关联的方法集。接口类型的方法集就是它自己。其他类型 T 的方法集由所有声明了接收者类型为 T 的方法组成。相应指针类型 *T 的方法集由所有声明了接收者类型为 *T 或 T 的方法组成(也就是说,它还包含了 T 的方法集)。

在你的例子中,接口类型 x 的方法集是 [foo(), bar()]。类型 Cat 的方法集是 [foo()],而类型 *Cat 的方法集是 [foo()] + [bar()] = [foo(), bar()]

这就解释了为什么变量 p 满足接口 x,而变量 c 不满足。

英文:

Let's look into the language specification:

> A type may have a <b>method set</b> 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 <b>also contains
> the method set</b> of T).

In your example, the method set of the interface type x is [foo(), bar()]. The method set of the type Cat is [foo()], and the method set of the type *Cat is [foo()] + [bar()] = [foo(), bar()].

This explains, why variable p satisfies the interface x, but variable c doesn't.

答案2

得分: 1

方法集

根据规范

> 任何其他命名类型 T 的方法集由所有接收器类型为 T 的方法组成。相应指针类型 *T 的方法集是所有接收器类型为 *T 或 T 的方法的集合(即它还包含 T 的方法集)。

在遵循可寻址和不可寻址类型的概念之前,方法集的定义听起来有些奇怪。

可寻址和不可寻址类型

如果值是可寻址类型,就可以在值上调用指针接收器方法。

> 与选择器一样,使用指针引用值接收器的非接口方法将自动解引用该指针:pt.Mv 等同于 (*pt).Mv。

> 与方法调用一样,使用可寻址值引用指针接收器的非接口方法将自动获取该值的地址:t.Mp 等同于 (&t).Mp。

在处理可寻址类型时(结构体是可寻址的),可以在值上调用指针接收器方法:

type Cat struct {}

func (c *Cat) bar() string { return "Mew" }

func main() {
    var c Cat
    c.bar()
}

接口类型的变量不可寻址

但并非所有的 Go 类型都是可寻址的。而且通过接口引用的变量是不可寻址的

无法在不可寻址类型的值上调用指针接收器方法:

type X interface {
    bar() string
}

type Cat struct{}

func (c *Cat) bar() string { return "Mew" }

/* 注意 `cat` 变量不是 `struct` 类型的值,而是 `X` 接口类型,因此它是不可寻址的。 */
func CatBar(cat X) { 
    fmt.Print(cat.bar())
}

func main() {
    var c Cat
    CatBar(c)
}

因此,使用以下错误,Go 运行时会防止段错误:

> 无法将 c(类型为 Cat)用作分配给 X 的类型:
Cat 未实现 X(bar 方法具有指针接收器)

英文:

Method set

Following the spec:

> The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type *T is the set of all methods with receiver *T or T (that is, it also contains the method set of T).

Method set definition sounds weird until you follow addressable and not addressable types concept.

Addressable and not addressable types

It is possible to call a pointer receiver method on a value if the value is of addressable type.

> As with selectors, a reference to a non-interface method with a value receiver using a pointer will automatically dereference that pointer: pt.Mv is equivalent to (*pt).Mv.

> As with method calls, a reference to a non-interface method with a pointer receiver using an addressable value will automatically take the address of that value: t.Mp is equivalent to (&t).Mp.

It is ok to call pointer receiver methods on values till you are dealing with addressable types (struct is addressable):

type Cat struct {}

func (c *Cat) bar() string { return &quot;Mew&quot; }

func main() {
    var c Cat
    c.bar()
}

Variables of interface type are not addressable

But not all Go types are addressable. Also variables referenced through interfaces are not addressable.

It is impossible to call pointer receiver on values of not addressable types:

type X interface {
    bar() string
}

type Cat struct{}

func (c *Cat) bar() string { return &quot;Mew&quot; }

/* Note `cat` variable is not a `struct` type value but
   it is type of `X` interface therefor it is not addressable. */
func CatBar(cat X) { 
    fmt.Print(cat.bar())
}

func main() {
	var c Cat
    CatBar(c)
}

So with the following error Go runtime prevents segment fault:

> cannot use c (type Cat) as type X in assignment:
Cat does not implement X (bar method has pointer receiver)

答案3

得分: 0

在dev.bmax的答案中添加一点内容。

type Cat struct{
}

func (c Cat) foo(){
  // do stuff_
}

func (c *Cat) bar(){
  // do stuff_
}

你可以这样做:

var c Cat
c.bar() // 可以调用bar(),因为c是一个变量。

但是不能这样做:

Cat{}.bar() // 不能调用bar(),因为c不是一个变量。

在类型为T的参数上调用T方法是合法的,只要参数是一个变量*;编译器会隐式地取其地址。但这只是语法糖:类型为T的值并不具有*T指针所具有的所有方法,因此它可能满足的接口更少。

另一方面,你始终可以使用Cat或*Cat调用foo()方法。

英文:

Add a little to dev.bmax's answer.

type Cat struct{
}

func (c Cat) foo(){
  // do stuff_
}

func (c *Cat) bar(){
  // do stuff_
}

you can do

var c cat
c.bar() // ok to call bar(), since c is a variable.

but not

cat{}.bar() // not ok to call bar(), c is not a variable.

It's legal to call a *T method on an argument of type T so long as the argument is a variable; the compiler implicitly takes its address. But this is mere syntactic sugar: a value of type T does not posses all methods that a *T pointer does, and as a result it might satisfy fewer interfaces.

On the other hand, you can always call foo() with Cat or *Cat.

答案4

得分: 0

这是什么意思?

package main

import (
    "fmt"
)

type Growler interface{
    Growl() bool
}

type Cat struct{
    Name string
    Age int
} 

// *Cat 适用于对象和“引用”(指向对象的指针)
func (c *Cat) Speak() bool{
    fmt.Println("喵喵!")
    return true
}

func (c *Cat) Growl() bool{
    fmt.Println("咆哮!")
    return true
}

func main() {
    var felix Cat // 不是指针
    felix.Speak() // 可以运行 :-)
    felix.Growl() // 可以运行 :-)

    var ginger *Cat = new(Cat) 
    ginger.Speak() // 可以运行 :-)
    ginger.Growl() // 可以运行 :-)
}
英文:

How about this?

package main

import (
    &quot;fmt&quot;
)

type Growler interface{
    Growl() bool
}

type Cat struct{
    Name string
    Age int
} 

// *Cat is good for both objects and &quot;references&quot; (pointers to objects)
func (c *Cat) Speak() bool{
    fmt.Println(&quot;Meow!&quot;)
        return true
}

func (c *Cat) Growl() bool{
    fmt.Println(&quot;Grrr!&quot;)
    return true
}

func main() {
    var felix Cat // is not a pointer
    felix.Speak() // works :-)
    felix.Growl() // works :-)

    var ginger *Cat = new(Cat) 
    ginger.Speak() // works :-)
    ginger.Growl() // works :-)
}

huangapple
  • 本文由 发表于 2017年1月29日 22:49:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/41922181.html
匿名

发表评论

匿名网友

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

确定