はじめに
値レシーバとポインタレシーバどちらを使うべきかをこの記事では解説します。
値レシーバの例
type customer struct {
balance float64
}
func (c customer) add(operation float64) {
c.balance += operation
}
func main() {
c := customer{balance: 100.0}
c.add(50.0)
fmt.Printf("balance: %.2f\n", c.balance) // balance: 100.00
}
customerのbalanceフィールドの値は変更されない。
ポインタレシーバの例
ポインタレシーバ
type customer struct {
balance float64
}
func (c *customer) add(operation float64) {
c.balance += operation
}
func main() {
c := customer{balance: 100.0}
c.add(50.0)
fmt.Printf("balance: %.2f\n", c.balance) // balance: 150.00
}
ポインタレシーバの場合は、ポインタ渡しとなる。
レシーバに対する変更は、元のオブジェクトに対して行われるので、customerのbalanceフィールが更新される。
レシーバがポインタでなければならいとき
- メソッドがレシーバを変更する必要があるとき
- メソッドのレシーバがコピーできないフィールドを含むとき(syncパッケージの方の一部)
type slice []int
func (s *slice) add (element int) {
*s = append(*s, element)
}
レシーバがポインタであるべきとき
- レシーバが大きな値のとき : 値渡しだとコピーされてしまうので
レシーバが値でなければならいとき
- レシーバの不変性を強制する必要があるとき
- レシーバがマップ、関数、チャネルのとき
レシーバが値であるべきとき
- レシーバが、変更する必要のないスライスのとき
- レシーバが小さな配列や、可変なフィールドを持たず必然的に値型であるtime.rime のような構造体のとき
- レシーバが int、float64、string といった基本データ型のとき
balanceはcustomer
構造体に直接あるのではなく、ポインタフィールドから参照される別の構造体の中にある。
type customer struct {
data *data
}
type data struct {
balance float64
}
func (c customer) add(operation float64) { //←値レシーバを使う
c.data.balance += operation
}
func main() {
c := customer{data: &data{
balance: 100,
}}
c.add(50.)
fmt.Printf("balance: %.2f\n", c.data.balance) // balance: 150.00
}
この場合はレシーバが値でもbalanceフィールドが変更される。
しかし、明示するという意味では、ポインタレシーバを使用する方が好ましい。
defer
メソッドに対して、deferを使用する場合、引数の評価について同じロジックが適用される。
つまり、レシーバはすぐに評価される。
以下は、値レシーバのメソッドをdeferで呼び出し、その後にレシーバを変更する例。
func main() {
s := &Struct{id: "foo"}
defer s.print() // sは即時評価される
s.id = "bar"
}
type Struct struct {
id string
}
func (s Struct) print() {
fmt.Println(s.id) // foo
}
s.id = "bar"
より先に、defer s.print()
のs
が評価されるので、代入されていることをdeferは見えません。
ポインタがレシーバの場合は、sが評価されるが、ポインタが参照している値が変更されているため、変更が反映される。
func main() {
s := &Struct{id: "foo"}
defer s.print()
s.id = "bar"
}
type Struct struct {
id string
}
func (s *Struct) print() {
fmt.Println(s.id) // bar
}
まとめ
正当な理由がない限りは、値レシーバを使用する。
ただし、迷ったときはポインタレシーバを使う方がいい。