阅读 844

[译] Go:方法接收者应该使用 T 还是 *T

这篇文章是我几天前在 Twitter 上提出的建议的延续。

在 Go 中,对于任何类型 T 都存在类型 *T,表示获取 T 类型(T 表示你声明的类型)变量的地址。例如:

type T struct { a int; b bool }
var t T    // t's type is T
var p = &t // p's type is *T
复制代码

这两种类型,T 和 *T 是不同的,*T 不能替代 T(此规则是递归的,**T 会返回 *T 地址指向的值)。

你可以在任何类型上声明方法;也就是说,你在 package 中声明了一个类型。因此,你可以在这个类型上声明一个方法,他的接收者可以使用 T 或者 *T。或者是说声明接收者的类型为 T 是为了获取接收者值的副本,声明接收者的类型为 *T 是为了获取指向接收者值的指针(Go 中的方法只是函数的语法糖,它将接收者作为第一个形式参数传递)。那么问题就变成了,我们应该选择哪种方式?(如果该方法不改变它的接收者,它是否需要方法这种形式?)

显然,如果你的方法改变了接收者,那应该声明 * T。但是,如果该方法不改变其接收者,是否可以将其声明为 T 呢?

事实证明,这样做的场景非常有限。例如,众所周知,你不应该复制 sync.Mutex 值,因为它会使互斥锁失效。由于互斥锁控制对数据的访问,它们经常被包含在结构中:

package counter

type Val struct {
        mu  sync.Mutex
        val int
}

func (v *Val) Get() int {
        v.mu.Lock()
        defer v.mu.Unlock()
        return v.val
}

func (v *Val) Add(n int) {
        v.mu.Lock()
        defer v.mu.Unlock()
        v.val += n
}
复制代码

大多数 Go 程序员都知道我们应该使用指针接收者 *Val ,并在其上声明 Get 或 Add 方法。但是,任何嵌入 Val 以利用其零值的类型,也必须把方法的接收者设为指针类型,否则它可能复制其嵌入类型的值。

type Stats struct {
        a, b, c counter.Val
}

func (s Stats) Sum() int {
        return s.a.Get() + s.b.Get() + s.c.Get() // whoops
}
复制代码

对于切片类型,可能也会发生类似的陷阱,当然也有可能发生意外的数据竞争

简而言之,我认为你更应该在 *T 上声明方法,除非你有充分的理由不这样做。

相关文章

  1. What is the zero value, and why is it useful?
  2. Ice cream makers and data races
  3. Slices from the ground up
  4. The empty struct
关注下面的标签,发现更多相似文章
评论