【问题标题】:Why can we assign a struct pointer to an interface variable even though the struct pointer does not implement the interface?为什么我们可以将结构指针分配给接口变量,即使结构指针没有实现接口?
【发布时间】:2021-01-07 18:17:47
【问题描述】:

我在下面介绍两个程序:程序 1 和程序 2。

我预计程序 1 无法编译,它确实无法编译。所以这很好。

我希望程序 2 编译失败,但它成功了!这个问题是关于为什么程序 2 会成功。

程序 1

https://play.golang.org/p/qX9nY8VLlx0

package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}

type Vertex struct {
    X float64
    Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    var a Abser
    
    a = Vertex{3, 4}
    fmt.Println(a.Abs())
}

编译失败,出现以下错误:

./prog.go:24:4: cannot use Vertex literal (type Vertex) as type Abser in assignment:
Vertex does not implement Abser (Abs method has pointer receiver)

我期待这个错误,因为*Vertex 实现了Abser,但Vertex 没有,所以我们不能将Vertex 对象分配给Abser 变量。

程序 2

https://play.golang.org/p/4bIs-fHGhYm

package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}

type Vertex struct {
    X float64
    Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    var a Abser
    
    a = &Vertex{3, 4}
    fmt.Println(a.Abs())
}

这编译成功。程序的输出是:

5

为什么会成功?这里,Vertex 实现了Abser,但*Vertex 没有实现Abser。然后我如何能够将*Vertex 类型的值分配给Abser

我需要了解哪些语言语义规则才能知道为什么会成功?

【问题讨论】:

  • 为指针设置的方法包括值接收器的所有方法。为值设置的方法不包括指针接收器的方法。请参阅规范中的Method Sets
  • @MuffinTop 我在play.golang.org/p/Kj5g6yJwI9s 的示例表明,虽然指针类型*T 的方法集包括接收器类型为*TT 的方法(这就是你所说的)。但是我看到同一个示例还表明,值类型 T 的方法集还包括接收器类型为 *TT 的方法,因为 v.AbsV()v.AbsP() 都成功了。我是否误解了结果?
  • 这是一个不同的属性。在可寻址值的情况下,如果方法需要指针接收器,调用会自动更改为(&x).m()。详情请见calls
  • 参见selector expressions 中的规则#3。有several questions 和这些主题的答案。
  • “我误解了结果吗?”是的。这甚至在 Go Tour of Go (tour.golang.org/methods/6) 的中途被覆盖,因为它解释了这类问题,所以值得一读。

标签: go types interface semantics


【解决方案1】:

assignability 要求说明了接口情况下需要什么:

值 x 可分配给 T 类型的变量(“x 可分配给 T") 如果以下条件之一适用:

... T 是一个接口类型,x 实现了 T。 ...

为了确定“x实现T”的规则,我们转向method sets的概念:

一个类型可能有一个与之关联的方法集。方法集 接口类型是它的接口。任何其他类型 T 的方法集 由所有声明为接收者类型 T 的方法组成。

乍一看,这意味着你第一个例子中Vertex的方法集是空的,你第二个例子中*Vertex的方法集也是空的。

但是,规范继续指定:

对应指针类型*T的方法集合是所有的集合 用接收者 *T 或 T 声明的方法(也就是说,它还包含 T)的方法集。

这意味着*Vertex 的方法集自动包含Vertex 的方法集,而不是相反

这对您的第一个示例没有帮助,但在第二个示例中,Vertex 的方法集确实包含Abs() float64。由于这就是实现Abser 接口所需的全部内容,因此*Vertex 被视为实现Abser

【讨论】:

    猜你喜欢
    • 2019-06-26
    • 2014-08-16
    • 1970-01-01
    • 1970-01-01
    • 2015-01-26
    • 2013-01-21
    • 2020-11-08
    • 1970-01-01
    • 2014-05-30
    相关资源
    最近更新 更多