满足接口的 Go 结构体方法的类型

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

Types of Go struct methods that satisfy an interface

问题

给定以下Go代码示例:

package main

import "fmt"

type greeter interface {
    hello()
    goodbye()
}

type tourGuide struct {
    name string
}

func (t tourGuide) hello() {
    fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
    fmt.Println("Goodbye", t.name)
}

func main() {
    var t1 tourGuide = tourGuide{"James"}
    t1.hello()   // Hello James
    t1.goodbye() // Goodbye James (same as (&t1).goodbye())

    var t2 *tourGuide = &tourGuide{"Smith"}
    t2.hello()   // Hello Smith
    t2.goodbye() // Goodbye Smith (same as (*t2).hello())

    // illegal: t1 is not assignable to g1 (why?)
    // var g1 greeter = t1

    var g2 greeter = t2
    g2.hello()   // Hello Smith
    g2.goodbye() // Goodbye Smith
}

我可以使用类型为tourGuide的变量t1或指向tourGuide的指针t2调用结构体tourGuide的两个方法。换句话说,我可以使用类型为T*T的变量调用具有T接收器的方法。同样地,如果T是可寻址的,我可以使用类型为T(如果T是可寻址的)或*T的变量调用具有*T接收器的方法。我理解编译器在这里处理了这些差异(请参见代码中的注释)。

然而,当我们实现接口时情况就不同了。在上述代码中,greeter接口类型的变量可以从tourGuide的指针赋值,但不能从tourGuide赋值。

有人能告诉我为什么会这样吗?为什么我可以调用t1.hello()t1.goodbye(),但是t1却不足以满足接口greeter的要求?

英文:

Given the following Go code example:

package main

import "fmt"

type greeter interface {
	hello()
	goodbye()
}

type tourGuide struct {
	name string
}

func (t tourGuide) hello() {
	fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
	fmt.Println("Goodbye", t.name)
}

func main() {
	var t1 tourGuide = tourGuide{"James"}
	t1.hello()   // Hello James
	t1.goodbye() // Goodbye James (same as (&t1).goodbye())

	var t2 *tourGuide = &tourGuide{"Smith"}
	t2.hello()   // Hello Smith
	t2.goodbye() // Goodbye Smith (same as (*t2).hello())

	// illegal: t1 is not assignable to g1 (why?)
	// var g1 greeter = t1

	var g2 greeter = t2
	g2.hello()   // Hello Smith
	g2.goodbye() // Goodbye Smith
}

I'm able to call the two methods of the struct tourGuide using either a variable of type tourGuide t1 or a pointer to tourGuide t2. In other words, I can call a method with T receiver using a variable of type T or *T. Similarly, I can call a method with *T receiver using a variable of type T (if T is addressable) or *T. I understand that the compiler handles the differences here (see my comments in the code).

However, things change when we are implementing interfaces. In the above code, a variable of type greeter interface is assignable from a pointer to tourGuide but not from a tourGuide.

Can anyone tell me why this is the case? Why am I able to call t1.hello() and t1.goodbye() but somehow t1 is not enough for the interface greeter?

答案1

得分: 8

如果一个方法有一个指针接收器,那么只能使用指针值作为接收器值。因此,要在某个值上调用这个方法,该值本身必须是一个指针,或者必须能够获取其地址(用作接收器)。

例如,如果你有一个变量,它是可寻址的,因此可以获取其地址并将其用作接收器。规范允许你这样做,这是自动发生的。

包装在接口中的值是不可寻址的。当创建一个接口值时,包装在接口中的值会被复制。因此,无法获取其地址。理论上,你可以允许获取副本的地址,但这将导致比它所提供的更多混淆,因为地址将指向一个副本,而具有指针接收器的方法只能修改副本而不是原始值。

请参考这个回答,详细说明/证明了在创建接口值时值是如何被复制的:https://stackoverflow.com/questions/36077566/how-can-a-slice-contain-itself/36078970#36078970

英文:

If a method has a pointer receiver, only a pointer value can be used as the receiver value. So to call this method on some value, the value itself must be a pointer, or it must be possible to acquire its address (to be used as the receiver).

If you have a variable for example, it is addressable and thus it's possible to get its address and use that as the receiver. And the spec allows you to do this, this happens automatically.

Values wrapped in interfaces are not addressable. When an interface value is created, the value that is wrapped in the interface is copied. It is therefore not possible to take its address. Theoretically you could allow to take the address of the copy, but that would be the source of (even) more confusion than what benefit it would provide, as the address would point to a copy, and methods with pointer receiver could only modify the copy and not the original.

See this answer which details / proves that values are copied when the interface value is created: https://stackoverflow.com/questions/36077566/how-can-a-slice-contain-itself/36078970#36078970

答案2

得分: 3

如果你有一个指向结构体的指针,Go语言允许你访问该结构体及其具有值类型接收器(而不是指针接收器)的函数的属性,而无需解引用指针。但是,这仅适用于一级指针,参见下面的代码,我将t2转换为指向指向tourguide的指针,此时我需要显式解引用它,将其重新转换为指向tourguide的指针。将结构体的一级指针视为一种特殊情况,Go语言允许你使用语法糖来访问值类型的属性和函数,以节省手动解引用变量的操作。

package main

import "fmt"

type greeter interface {
    hello()
    goodbye()
}

type tourGuide struct {
    name string
}

func (t tourGuide) hello() {
    fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
    fmt.Println("Goodbye", t.name)
}

func main() {
    var t1 tourGuide = tourGuide{"James"}
    t1.hello()   // Hello James
    t1.goodbye() // Goodbye James (same as (&t1).goodbye())

    var tmpT2 *tourGuide = &tourGuide{"Smith"}
    var t2 **tourGuide = &tmpT2
    (*t2).hello()   // Hello Smith
    (*t2).goodbye() // Goodbye Smith (same as (*t2).hello())

    //illegal: t1 is not assignable to g1 (why?)
    //var g1 greeter = t1

    //now this is illegal too
    //var g2 greeter = t2

    var g3 greeter = (*t2)
    g3.hello()   // Hello Smith
    g3.goodbye() // Goodbye Smith
}
英文:

if you have a pointer to a struct then go will allow you to access the properties on the struct and its functions which have value type receivers (as apposed to pointer receivers) without having to dereference your pointer, but this only works for one level of pointer, see your code below where I turn t2 into a pointer to a pointer to a tourguide, at this point I need to explicitly dereference it to make it back into a pointer to a tourguide. think of the first level of a pointer to a struct as being a special case that go allows you to use syntatic sugar to access the value types properties and functions to save you having to constantly manually dereference you variables.

package main

import "fmt"

type greeter interface {
    hello()
    goodbye()
}

type tourGuide struct {
    name string
}

func (t tourGuide) hello() {
    fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
    fmt.Println("Goodbye", t.name)
}

func main() {
    var t1 tourGuide = tourGuide{"James"}
    t1.hello()   // Hello James
    t1.goodbye() // Goodbye James (same as (&t1).goodbye())

    var tmpT2 *tourGuide = &tourGuide{"Smith"}
    var t2 **tourGuide = &tmpT2
    (*t2).hello()   // Hello Smith
    (*t2).goodbye() // Goodbye Smith (same as (*t2).hello())

    //illegal: t1 is not assignable to g1 (why?)
    //var g1 greeter = t1

    //now this is illegal too
    //var g2 greeter = t2

    var g3 greeter = (*t2)
    g3.hello()   // Hello Smith
    g3.goodbye() // Goodbye Smith
}

答案3

得分: 1

这是要翻译的内容:

我的回答 解释了为什么Go语言不允许你获取存储在接口中的值的地址。

简而言之,这是因为当接口中随后存储了不同类型 B 的值时,指向接口中类型为 A 的值的指针将会失效。

英文:

My answer here explains why Go prevents you from taking the address of a value stored in an interface.

tl;dr its because a pointer to A which points to a value of type A in an interface would be invalidated when a value of different type B is subsequently stored in the interface.

huangapple
  • 本文由 发表于 2016年12月7日 18:33:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/41015114.html
匿名

发表评论

匿名网友

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

确定