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

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

embedded interface in a struct behaves in a very weird manner

问题

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

type TestInterface interface {
   Walk()
}

type A struct {
}

func (a *A) Walk() {
   fmt.Println("hello world")
}

type B struct {
   TestInterface
}

func main() {
    var a *A
    b := B{}

    a.Walk() // 即使a是nil,这里不会引发panic
    b.Walk() // 这里会引发panic
}

由于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.

type TestInterface interface {
   Walk()
}

type A struct {
}

func (a *A) Walk() {
   fmt.Println("hello world")
}

type B struct {
   TestInterface
}

func main() {
    var a *A
    b := B{}

    a.Walk() // This will not panic even though a is nil
    b.Walk() // This will panic.
}

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。

func main() {
	var a *A
	b := B{TestInterface:a}

	fmt.Println(reflect.TypeOf(a), b)
	a.Walk() // 即使a为nil,这不会引发panic
	b.Walk() // 现在这不会再引发panic。
}

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.

func main() {
	var a *A
	b := B{TestInterface:a}

	fmt.Println(reflect.TypeOf(a), b)
	a.Walk() // This will not panic even though a is nil
	b.Walk() // This will not panic anymore.
}

run in playground

答案2

得分: 0

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

type TestInterface interface {
   Walk()
}

type A struct {
}

func (a *A) Walk() {
   fmt.Println("hello world")
}

type B struct {
   TestInterface
}

func main() {
    var a *A
    
    b := B{TestInterface: a}
    
    // 两者都不会引发恐慌
    a.Walk() 
    // 由于a是nil,TestInterface仍然是nil。
    b.Walk() 
}
英文:

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.

type TestInterface interface {
   Walk()
}

type A struct {
}

func (a *A) Walk() {
   fmt.Println("hello world")
}

type B struct {
   TestInterface
}

func main() {
    var a *A
    
    b := B{TestInterface: a}
    
    // both will not panic
    a.Walk() 
    // TestInterface is still nil as a is nil.
    b.Walk() 
}```

</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:

确定