前言

最近在做一個 Side Project 時發覺,自己對 Golang Pointers 的使用時機其實不太清楚。

大部分時候都是滿隨意的使用,於是想找些資訊來釐清一下什麼時候該使用,什麼時候可能可以選擇其他做法。

之前其實也寫過一篇,關於 Pointer 的好文分享,這篇有滿多內容類似的,但本文的講解算是比較清晰。

資料來源:Go Pointers: When to Use Pointers | by Kent Rancourt | Medium

以下也是節錄我覺得重要的部分,如果想要看更完整的內容可以去看看原文囉。

用 Pointers 會遇到的兩個大問題

  1. 如果收到 nil pointer 會引發 panic
type S struct {
	Name string
}

func main() {
	var s *S
	fmt.Println(s.Name)
}

// example 2

func main() {
	// ...
	s := storage.GetS("foobar")
	fmt.Println(s.Name)
}

func GetS(string id) *S {
  // ...
}

回傳的 Pointer 可能會是 nil,正確的作法應該是使用前請先搖一搖檢查,但其實大部分人並不會。

  1. 改到你不想改的資料
type S struct {
	Name string
}

func printUpper(s *S) {
	s.Name = strings.ToUpper(s.Name)
	fmt.Println(s.Name)
}

func main() {
	s := &S{
		Name: "foo",
	}
	printUpper(s)
	fmt.Println(s.Name)
}

以 printUpper 這個 function 的意圖來看,其實大可不必使用 pointer,使用一般的 struct 即可。

使用 Pointer 的建議

一律不要一開始就使用 Pointer ,直到你確認要用到再作修改。

Pointer 的迷思

大家會認為使用 Pointer 會比較有效率的原因是因為當使用 pass by value 的時候,其實 Golang 都會使用 copy,但當你 copy 一個很大的 struct 時,就會很沒效率。

但是在 Golang 為什麼這件事變得這麼不一定呢?

簡單來說(如有錯誤請指正)

那是因為,一般 function 的變數通常都會暫存在 stack 內,隨著 function scope 結束,這段記憶體就會被 OS 重新指派。

但當我們回傳 pointer 的時候,Golang 會去判斷是否要把這個東西移動至 heap 。

可是 heap 的管理是由語言本身去做管理,以 Golang 來說,他有自己的 Garbage Collector ,他會需要去處理 heap 的相關事務。

當 GC 花越多時間,你的服務效能就越差。

所以,其實你要知道有些情況到底是使用 Pointer 好還是 Struct ,最正確的方法是去跑 benchmarking 。

那如果不回傳 pointer 我要怎麼做修正呢?

已前面的例子來說,他回傳 pointer 有一個可能的原因是因為如果找不到該值就回傳 nil 。

但我們可以改成這樣

func GetS(id string) (S, bool) {
	// Look for the item
	// ...
	if found {
		return item, true
	}
	return S{}, false
}

fucn main() {
    s, err := GetS("id")
    // ....

    // or 
    s, _ := GetS("id)
    //...
}

我們可以看到這樣,即使 caller 選擇忽略 error 。但同時也證明,他知道這裡有 error 會出現。

那什麼時候使用 Pointer ?

  1. 沒有其他選擇了

像是很多 Library 他的設計就是透過 Pointer 才能得到結果,那就用吧。

  1. Struct 需要修改他本身的值

這裡原文舉的例子是

type S struct {
	Name string
}

func (s S) SetName(name string) {
	s.Name = name
}

func main() {
	s := S{
		Name: "foo",
	}
	s.SetName("bar")
	fmt.Println(s.Name)
}

老實說我覺得這也有解決方案,比如說回傳 struct ,然後重新 resign 。

但,你可以理解他的意思,因為 function receiver 如果你使用 struct ,他其實也是一個 copy 。

  1. You need a singleton

當有一些物件就是只能使用同一個時,比如說資料庫等等。

小結

看完了之後,有沒有更清晰的了解什麼時候使用 Pointer 是更好的選擇啊?

如果有任何錯誤的地方,請在下面留言或來信指正。

我們下次見。

延伸閱讀

Stack vs Heap: Know the Difference

Understanding Allocations in Go: the stack, the heap, allocs/op, trace, and more | Eureka Engineering