3
2
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【Go言語】値レシーバ v.s. ポインタレシーバ

Last updated at Posted at 2024-07-15

はじめに

値レシーバとポインタレシーバどちらを使うべきかをこの記事では解説します。

値レシーバの例

Go Playground

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フィールドの値は変更されない。

ポインタレシーバの例

Go Playground

ポインタレシーバ
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構造体に直接あるのではなく、ポインタフィールドから参照される別の構造体の中にある。

Go Playground

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で呼び出し、その後にレシーバを変更する例。

Go Playground

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が評価されるが、ポインタが参照している値が変更されているため、変更が反映される。

Go Playground

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
}

まとめ

正当な理由がない限りは、値レシーバを使用する。
ただし、迷ったときはポインタレシーバを使う方がいい。

参考文献

  1. https://100go.co/ja/#42
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2