【问题标题】:Why can't I assign type's value to an interface implementing methods with receiver type pointer to that type?为什么我不能将类型的值分配给接口实现方法,接收器类型指针指向该类型?
【发布时间】:2016-11-05 03:15:25
【问题描述】:

我在 Golang 世界里已经 2 天了,并且正在经历 go tour。我不禁注意到一个我似乎无法通过适当推理来达成一致的特殊性。

这段代码运行完美:

package main
import (
    "fmt"
    "math"
)
type Vertex struct{
    X,Y float64
}
type Abser interface{
    Abs() float64
}
func (v Vertex) Abs() float64{ //method with value receiver argument
    return math.Sqrt(v.X*v.X+v.Y*v.Y)
}
func main(){
    var myVer Vertex = Vertex{3,4}
    var inter Abser
    inter = &myVer //assigning *Vertex type to inter
    fmt.Println(inter.Abs())
}

同时,以下代码显示错误:

package main
import (
    "fmt"
    "math"
)
type Vertex struct{
    X,Y float64
}
type Abser interface{
    Abs() float64
}
func (v *Vertex) Abs() float64{ //method with pointer receiver argument
    return math.Sqrt(v.X*v.X+v.Y*v.Y)
}
func main(){
    var myVer Vertex = Vertex{3,4}
    var inter Abser
    inter = myVer //assigning Vertex type to inter
    fmt.Println(inter.Abs())
}

错误是:

interface.go:18:不能在赋值中使用 myVer(类型 Vertex)作为类型 Abser: Vertex没有实现Abser(Abs方法有指针接收器)

直到游览的这一部分,我才明白围棋的创造者已经放弃了像这样的繁琐符号

(*v).method1name()

(&v).method2name()

这样带有值接收器的方法可以同时用于值和指针,反之亦然。

为什么在使用接口时语言会区分两者(值和指针)?如果在这里可以应用相同的引用/取消引用原则,会不会更方便?

我希望我没有遗漏一些太明显的东西。 谢谢!

【问题讨论】:

标签: go


【解决方案1】:

Intro++ to Go Interfaces”说明了这个问题:

*Vertex 是一种类型。它是“指向Vertex”类型的指针。这是与(非指针)Vertex 不同的类型。关于它是指针的部分是它的类型的一部分。

您需要类型的一致性。

Methods, Interfaces and Embedded Types in Go”:

确定接口合规性的规则取决于这些方法的接收者以及接口调用的方式。
Here are the rules in the spec for how the compiler determines if the value or pointer for our type implements the interface:

  • 对应指针类型*T的方法集是接收者*TT的所有方法的集合

这条规则表明,如果我们用来调用特定接口方法的接口变量包含一个指针,那么具有基于值和指针的接收器的方法将满足该接口。

  • 任何其他类型T的方法集由接收器类型为T的所有方法组成。

这条规则是说,如果我们用来调用特定接口方法的接口变量包含一个值,那么只有具有基于值的接收者的方法才能满足该接口。

Karrot Kakeanswer 关于method set 也在go wiki 中有详细说明:

接口类型的方法集就是它的接口。

存储在接口中的具体值不可寻址,就像map 元素不可寻址一样。
因此,当您在接口上调用方法时,它必须具有相同的接收器类型,或者必须可以与具体类型直接区分。

如您所料,指针和值接收器方法可以分别用指针和值调用。
值接收器方法可以用指针值调用,因为它们可以先被取消引用 .
指针接收器方法不能使用值调用,因为存储在接口中的值没有地址

("没有地址"其实就是not addressable)

【讨论】:

  • Intro++ to Go Interfaces 非常清楚地解决了我的疑问。它明确指出:“.. 接口中的值位于隐藏的内存位置,因此编译器无法自动为您获取指向该内存的指针(在 Go 语言中,这被称为“不可寻址”) 。”我想建议任何面临与我相同的疑问的人,访问该链接并浏览一次,这将是值得您花时间的。 @VonC,感谢您提供的精彩链接 :-)
  • @TanmayGarg 是的,我在最后一次编辑答案中详细说明了不可寻址的部分。
  • 啊,是的。谢谢,再次:-)
【解决方案2】:

指针类型的method set包括值接收器方法,但为值设置的方法不包括指针接收器方法。为值类型设置的方法不包括指针接收器方法,因为存储在接口中的值不是addressable

【讨论】:

    【解决方案3】:

    好问题。
    这是 Golang 类型系统:Vertex*Vertex 是不同的类型
    请参阅此澄清示例:
    你可以使用type Vertex2 Vertex 来定义新的Vertex2 类型,这些也是不同的类型:

    在此过程中:

    package main
    
    import "fmt"
    import "math"
    
    func main() {
        var myVer Vertex = Vertex{3, 4}
        var inter Abser = myVer //assigning Vertex type to inter
        fmt.Println(inter.Abs())
    }
    
    func (v Vertex) Abs() float64 { //method with pointer receiver argument
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    type Vertex struct {
        X, Y float64
    }
    type Abser interface {
        Abs() float64
    }
    

    这行不通:

    package main
    
    import "fmt"
    import "math"
    
    func main() {
        var myVer Vertex = Vertex{3, 4}
        var inter Abser = myVer //assigning Vertex type to inter
        fmt.Println(inter.Abs())
    }
    
    type Vertex2 Vertex
    
    func (v Vertex2) Abs() float64 { //method with pointer receiver argument
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    type Vertex struct {
        X, Y float64
    }
    type Abser interface {
        Abs() float64
    }
    

    错误是:

    .\m.go:8:不能在赋值中使用 myVer(Vertex 类型)作为 Abser 类型:
    Vertex 没有实现 Abser(缺少 Abs 方法)

    与您的示例完全相同的错误:

    package main
    
    import "fmt"
    import "math"
    
    func main() {
        var myVer Vertex = Vertex{3, 4}
        var inter Abser = myVer //assigning Vertex type to inter
        fmt.Println(inter.Abs())
    }
    
    func (v *Vertex) Abs() float64 { //method with pointer receiver argument
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    type Vertex struct {
        X, Y float64
    }
    type Abser interface {
        Abs() float64
    }
    

    因为两个 Vertex 和 Vertex2 是不同的类型。
    并且这也有效

    package main
    
    import "fmt"
    import "math"
    
    func main() {
        var inter Abser = &Vertex{3, 4} //assigning &Vertex type to inter
        fmt.Printf("inter: %#[1]v\n", inter)
        fmt.Println(inter.Abs())
    }
    
    func (v Vertex) Abs() float64 { //method with pointer receiver argument
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    type Vertex struct {
        X, Y float64
    }
    type Abser interface {
        Abs() float64
    }
    

    输出:

    inter: &main.Vertex{X:3, Y:4}
    5
    

    因为:
    一个类型可能有一个与之关联的方法集。接口类型的方法集就是它的接口。 任何其他类型 T 的方法集由所有声明为接收者类型 T 的方法组成。对应指针类型 *T 的方法集是所有声明为接收者 *T 或 T 的方法集(即它也包含T)的方法集。进一步的规则适用于包含匿名字段的结构,如结构类型部分所述。任何其他类型都有一个空方法集。在方法集中,每个方法都必须有一个唯一的非空方法名。

    类型的方法集决定了该类型实现的接口以及可以使用该类型的接收器调用的方法。
    参考:https://golang.org/ref/spec#Method_sets

    【讨论】:

      【解决方案4】:

      存储在接口中的值不可寻址 - 但为什么呢?请参阅here 以获取答案。

      tl;dr 这是因为当不同类型 B 的值随后存储在接口中时,指向接口中 A 类型值的 A 指针将失效。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-28
        • 1970-01-01
        • 2011-09-08
        • 1970-01-01
        • 1970-01-01
        • 2016-08-01
        • 2019-11-01
        • 1970-01-01
        相关资源
        最近更新 更多