关于go:为接口分配值会复制任何内容吗?

 2020-01-14 

Does assigning value to interface copy anything?

我一直在努力围绕Go中接口的概念。 阅读此内容对此有很大帮助。

唯一让我感到不适的是语法。 看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import"fmt"

type Interface interface {
    String() string
}

type Implementation int

func (v Implementation) String() string {
    return fmt.Sprintf("Hello %d", v)
}

func main() {
    var i Interface
    impl := Implementation(42)
    i = impl
    fmt.Println(i.String())
}

我的问题是i = impl。 基于一个接口实例实际上拥有一个指向实际数据的指针引用这一事实,对我来说i = &impl会更自然。 通常,在不使用&时分配非指针将形成数据的完整内存副本,但是在分配给接口时,这似乎避开了它,而是简单地(在后台)将指针分配给接口值。 我对吗? 也就是说,int(42)的数据将不会复制到内存中吗?


int(42)的数据将被复制。试试这个代码:

1
2
3
4
5
6
7
8
func main() {
    var i Interface
    impl := Implementation(42)
    i = impl
    fmt.Println(i.String())
    impl = Implementation(91)
    fmt.Println(i.String())
}

(游乐场链接)

您会发现第二个i.String()仍然显示42。 Go的棘手方面之一可能是方法接收者也可以是指针。

1
2
3
4
5
6
func (v *Implementation) String() string {
    return fmt.Sprintf("Hello %d", *v)
}

// ...
i = &impl

如果要接口保留指向impl原始值的指针,这就是您想要的。接口"在幕后"是一个结构,它持有指向某些数据的指针,或者指向数据本身(以及一些我们可以出于目的而忽略的类型元数据)的指针。如果数据大小小于或等于一个机器字(无论是指针,结构还是其他值),则将存储数据本身。

否则它将是指向某些数据的指针,但这是棘手的部分:如果实现接口的类型是struct,则指针将指向该结构的副本,而不是分配给接口变量本身的结构。或至少在语义上用户可以这样认为,优化可能会导致在两个值分开之前(例如,直到您调用String或重新分配impl)才可以复制该值。

简而言之:分配给接口可以在语义上被认为是实现接口的数据的副本。如果这是指向类型的指针,则复制该指针,如果是大结构,则复制该大结构。在后台使用指针的接口的详细信息是出于垃圾收集的原因,并确保堆栈以可预测的数量扩展。就开发人员而言,应将它们视为分配的实现类型的特定实例的语义副本。