[譯] Go:方法接收者應該使用 T 還是 *T
阿新 • • 發佈:2019-12-31
- 原文地址:dave.cheney.net/2016/03/19/…
- 原文作者:Dave Cheney
- 譯文地址:github.com/watermelo/d…
- 譯者:咔嘰咔嘰
- 譯者水平有限,如有翻譯或理解謬誤,煩請幫忙指出
這篇文章是我幾天前在 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 上宣告方法,除非你有充分的理由不這樣做。