1. 程式人生 > 程式設計 >[譯] Go:方法接收者應該使用 T 還是 *T

[譯] 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