Go 語言內 struct methods 該使用 pointer 或 value 傳值?

上週末在台北講『Go 語言基礎課程』,其中一段介紹 Struct 的使用,發現有幾個學員對於在 Method 內要放 Pointer 或 Value 感到困惑,而我自己平時在寫 Go 語言也沒有注意到這點。好在強者學員 Dboy Liao 找到一篇說明:『Don’t Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang』,在 Go 語言如何區分 func (s *MyStruct)func (s MyStruct),底下我們先來看看簡單的 Struct 例子

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

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println(c.Price)
}

func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
}

上面是個很簡單的 Go struct 例子,假設我們需要動態更新 Price 值,可以新增 UpdatePrice method。線上執行範例

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

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println("price:", c.Price)
}

func (c Cart) UpdatePrice(price int) {
    c.Price = price
}

func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
    c.UpdatePrice(200)
    c.GetPrice()
}

上面可以看到輸出的結果是 100,只用 value 傳值是無法改 Struce 內成員。我們可以用另外方式繞過。線上執行範例

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

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println("price:", c.Price)
}

func (c Cart) UpdatePrice(price int) *Cart {
    c.Price = price
    return &c
}

func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
    c = c.UpdatePrice(200)
    c.GetPrice()
}

從上面範例可以發現,將 struct 回傳,這樣就可以正確拿到修改的值。但是這解法不是我們想要的。來試試看用 Pointer 方式 線上執行範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println("price:", c.Price)
}

func (c Cart) UpdatePrice(price int) {
    fmt.Println("[value] Update Price to", price)
    c.Price = price
}

func (c *Cart) UpdatePricePointer(price int) {
    fmt.Println("[pointer] Update Price to", price)
    c.Price = price
}

func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
    c.UpdatePrice(200)
    fmt.Println(c)
    c.UpdatePricePointer(200)
    fmt.Println(c)
}

只要使用 pointer 方式傳值就可以正確將您需要改變的值寫入,所以這邊可以結論就是,如果只是要讀值,可以使用 Value 或 Pointer 方式,但是要寫入,則只能用 Pointer 方式。其實在 Go 語言官方有整理 FAQ,竟然之前都沒發現,參考底下官方給的建議。

寫入或讀取

如果您需要對 Struct 內的成員進行修改,那請務必使用 Pointer 傳值,相反的,Go 會使用 Copy struct 方式來傳入,但是用此方式你就拿不到修改後的資料。

效能

假設 Struct 內部成員非常的多,請務必使用 Pointer 方式傳入,這樣省下的系統資源肯定比 Copy Value 的方式還來的多。

一致性

在開發團隊內,如果有人使用 Pointer 有人使用 Value 方式,這樣寫法不統一,造成維護效率非常低,所以官方建議,全部使用 Pointer 方式是最好的寫法。


See also