【问题标题】:About the interface assignment关于接口分配
【发布时间】:2020-01-25 07:35:08
【问题描述】:

当结构指针分配给接口时,为什么 Go 不认为是类型不匹配错误?

package main

import "fmt"

type ABC interface {
    a() string
    b() int
}

type XYZ struct {
    aa string
    bb int
}

func (xyz XYZ) a() string {
    return "XYZ"
}

func (xyz XYZ) b() int {
    return 123
}

func main() {
    var xyz *XYZ
    var abc ABC = xyz // type of abc is *main.XYZ,I think that Golang can find this error here, but why not?

    fmt.Printf("%T\n", abc)

    a, ret := abc.(*XYZ)
    fmt.Println(a, ret) // type of a is *main.XYZ

    fmt.Println(a.a()) // will occur a error, because the type of a(*main.XYZ) not implements the interface ABC
}

我想知道为什么 Go 不认为这是“var abc ABC = xyz”处的错误

【问题讨论】:

  • XYZ 很好地实现了接口 ABC,所以我想您可能误解了 Go 接口的工作原理?如果一个结构具有接口指定的所有方法,它会实现它而无需您对此做任何事情。这就是你所做的,XYZ拥有ABC指定的所有方法,即a() stringb() int
  • 谢谢,我定义了a()和b()的接收者,是XYZ,不是*XYZ。 XYZ.a() 没问题,*XYZ.a() 不起作用。所以,我认为 *XYZ 没有实现 ABC。

标签: pointers go methods interface


【解决方案1】:

XYZ 确实实现了 ABC。这与method sets 的确定方式有关(强调):

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

The method set determines whether an interface is implemented:

接口类型指定了一个称为其接口的方法集。接口类型的变量可以存储任何类型的值,方法集是接口的任何超集。这种类型被称为实现接口

调用*XYZ.a()时,Go 编译器总是可以自动解引用指针以获取值接收者。这样做没有缺点,因为无法修改接收者(就调用者而言)。

当且仅当值是可寻址的时,反之才为真:

type T struct {}
func (*T) M()

func main() {
    var t T
    t.M() // ok; t is addressable and the compiler rewrites this to (*t).M()

    var m map[string]T
    m["x"].M() // error: cannot take the address of m["x"]
}

另见:Golang Method Sets (Pointer vs Value Receiver)

【讨论】:

    【解决方案2】:

    会报错,因为a(*main.XYZ)的类型没有实现接口ABC

    错了。 *main.XYZ 实现 ABC(否则 abc = xyz 将在编译时失败,例如尝试将方法 b 重命名为 c),但变量 a 拥有一个 nil 指针(类型*XYZ)。由于XYZ.a() 方法有值接收器,要调用它,必须取消引用*XYZ 类型的指针值。但是 nil 指针什么都没有指向,它不能被取消引用,尝试这样做会导致运行时恐慌,就像您所经历的那样。

    如果您在开头使用非nil 指针初始化xyz,它会起作用:

    var xyz *XYZ = new(XYZ)
    

    Go Playground 上试用。

    还要注意,如果XYZ.a()XYZ.b() 有指针接收器,那么如果xyznil,它也可以工作:

    func (xyz *XYZ) a() string {
        return "XYZ"
    }
    
    func (xyz *XYZ) b() int {
        return 123
    }
    
    func main() {
        var xyz *XYZ
        // ...
    

    Go Playground 上试试。这样做的原因是,如果接收器是指针,则不必取消引用 nil 指针以调用具有指针接收器的方法,因此不会发生运行时恐慌。当然,如果您在方法中引用 XZY.aaXYZ.bb 字段,那将是运行时恐慌,但您当前的方法实现并没有这样做,所以它可以工作。

    【讨论】:

    • 谢谢,Golang 可以在指向 nil 值(但类型不同,XYZ)的指针上调用方法 a(),它会将 nil 作为接收者传递给 a()。所以,Go 应该知道 *XYZ 不是 a() 的接收者,它可以确定 a() 不能在 *XYZ 上调用。
    • 当然,我已经像你一样尝试过,在我问问题之前,只需修改 (xyz XYZ) a() => (xyz *XYZ) a() ,它会正常工作,因为接收器匹配。我在想:既然Golang允许这种情况,为什么我把*XYZ赋值给abc时不会出错?
    • @JoeRadar 这与匹配无关。如果方法具有非指针接收器,则 XYZ*XYZ 类型都实现了该接口。问题是不能取消引用 nil 指针来调用非指针方法。如果指针被初始化,它也适用于非指针方法。都写在答案里了。
    • 也许我明白你所说的一切,但我想我可以回答这个问题。当我调用一个实现的结构方法时
    【解决方案3】:

    @icza,也许我不明白你说的所有内容,但我想我现在可以回答这个问题了。当我在值上调用方法时(不是指针,实现接口的结构的方法,方法的接收者是指针)。 Golang 将创建一个新对象并从原始结构值复制值,然后,iface.data 将指向新对象,现在,当我们将新对象的指针传递给方法时,它可以被修改,但这操作不会改变原始结构值,这没有任何用处,所以当我们将结构值分配给接口(指针接收器)时,Golang会发生错误

    【讨论】:

      猜你喜欢
      • 2011-10-05
      • 2018-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-05
      • 2017-05-04
      相关资源
      最近更新 更多