嵌入式接口在结构体中的行为非常奇怪。

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

embedded interface in a struct behaves in a very weird manner

问题

我发现很难理解Go语言的内部工作原理。有些情况下,它会表现出奇怪的行为。

  1. type TestInterface interface {
  2. Walk()
  3. }
  4. type A struct {
  5. }
  6. func (a *A) Walk() {
  7. fmt.Println("hello world")
  8. }
  9. type B struct {
  10. TestInterface
  11. }
  12. func main() {
  13. var a *A
  14. b := B{}
  15. a.Walk() // 即使a是nil,这里不会引发panic
  16. b.Walk() // 这里会引发panic
  17. }

由于b嵌入了TestInterface,所以b.Walk()会以类似a.Walk()的方式在结构体A上调用Walk方法。

为什么其中一个能正常工作,而另一个会引发panic呢?

英文:

I find hard to understand how Go works internally. There are some cases where it exhibits weird behavior.

  1. type TestInterface interface {
  2. Walk()
  3. }
  4. type A struct {
  5. }
  6. func (a *A) Walk() {
  7. fmt.Println("hello world")
  8. }
  9. type B struct {
  10. TestInterface
  11. }
  12. func main() {
  13. var a *A
  14. b := B{}
  15. a.Walk() // This will not panic even though a is nil
  16. b.Walk() // This will panic.
  17. }

Since b embeds TestInterface, b.Walk() will internally call Walk method on struct A in a similar way as a.Walk() is called.

Why is it then that one works and the other panics?

答案1

得分: 2

根据Go文档关于Method_declarations的说明,方法与接收器的基本类型绑定。

接收器是通过在方法名之前指定的额外参数部分来指定的。该参数部分必须声明一个非可变参数,即接收器。其类型必须是定义的类型T或定义的类型T的指针。T被称为接收器的基本类型。接收器的基本类型不能是指针或接口类型,并且它必须在与方法相同的包中定义。该方法被称为绑定到其接收器的基本类型,并且方法名仅在类型T或*T的选择器中可见。

因此,你的方法绑定到main.A类型,而var a的类型也是main.A。因此,可以调用Walk()方法。但是,如果在方法内部将a用作值,它也会引发panic。

B类型的b中,TestInterface的默认类型为nil,这就是为什么会引发panic的原因。你必须将实现接口的类型注入到B中,以避免panic。

  1. func main() {
  2. var a *A
  3. b := B{TestInterface:a}
  4. fmt.Println(reflect.TypeOf(a), b)
  5. a.Walk() // 即使a为nil,这不会引发panic
  6. b.Walk() // 现在这不会再引发panic。
  7. }

playground中运行

英文:

As Go doc mentioned about Method_declarations, Methods are bound to the base type of the receiver.

> The receiver is specified via an extra parameter section preceding the
> method name. That parameter section must declare a single non-variadic
> parameter, the receiver. Its type must be a defined type T or a
> pointer to a defined type T. T is called the receiver base type. A
> receiver base type cannot be a pointer or interface type and it must
> be defined in the same package as the method. The method is said to be
> bound to its receiver base type and the method name is visible only
> within selectors for type T or *T.

So your Methods are bound to *main.A type and type of var a also *main.A. So it is allowed to call Walk() method. But if you use a as a value inside the method, it will also panic.

Inside your b of B type, TestInterface's default type is nil and that's why It is panicing. You have to inject interface implemented type into B to avoid panicing.

  1. func main() {
  2. var a *A
  3. b := B{TestInterface:a}
  4. fmt.Println(reflect.TypeOf(a), b)
  5. a.Walk() // This will not panic even though a is nil
  6. b.Walk() // This will not panic anymore.
  7. }

run in playground

答案2

得分: 0

感谢@mkopriva上面的评论,我按照以下方式修改了程序,现在它是有意义的。这也意味着在Go语言中,nil具有与之关联的类型。

  1. type TestInterface interface {
  2. Walk()
  3. }
  4. type A struct {
  5. }
  6. func (a *A) Walk() {
  7. fmt.Println("hello world")
  8. }
  9. type B struct {
  10. TestInterface
  11. }
  12. func main() {
  13. var a *A
  14. b := B{TestInterface: a}
  15. // 两者都不会引发恐慌
  16. a.Walk()
  17. // 由于a是nil,TestInterface仍然是nil。
  18. b.Walk()
  19. }
英文:

Thanks to @mkopriva comment above, I modified the program in the following way and it makes sense now. It also means that nil in Go has a type associated with it.

  1. type TestInterface interface {
  2. Walk()
  3. }
  4. type A struct {
  5. }
  6. func (a *A) Walk() {
  7. fmt.Println("hello world")
  8. }
  9. type B struct {
  10. TestInterface
  11. }
  12. func main() {
  13. var a *A
  14. b := B{TestInterface: a}
  15. // both will not panic
  16. a.Walk()
  17. // TestInterface is still nil as a is nil.
  18. b.Walk()
  19. }```
  20. </details>

huangapple
  • 本文由 发表于 2021年9月2日 11:02:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/69023606.html
匿名

发表评论

匿名网友

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

确定