ポインタレシーバ
星印を1つ足す
前のレッスンで、メソッドの中で書き換えても効かない問題がありました。解決策は1文字です。
Go
func (i Item) MarkSoldOut() // 効かない
func (i *Item) MarkSoldOut() // 効く*Item は「Item そのものではなく、Item の居場所」を表します。
Go
func (i *Item) MarkSoldOut() {
i.SoldOut = true
}
item.MarkSoldOut()
fmt.Println(item.SoldOut) // true。効いているなぜ効くのか
値レシーバは、呼ぶたびに中身をコピーして渡します。メソッドの中の i は写しなので、書き換えても元には届きません。
ポインタレシーバは、どこにあるかだけを渡します。メソッドの中の i は元と同じ場所を指しているので、書き換えが元に届きます。
プレーンテキスト
値レシーバ 写しを渡す 書き換えても元は無傷
ポインタレシーバ 場所を渡す 書き換えると元も変わる第5章の配列とスライスの違いと、同じ構図です。
呼び出し側は同じ
Go
item.MarkSoldOut() // どちらでもこう書く他の言語なら &item と書く必要がある場面ですが、Go は自動で補います。呼ぶ側からは見分けが付きません。
楽な反面、メソッドの定義を見ないと書き換わるのかどうか分からない、ということでもあります。
どちらを使うか
判断は2つです。
プレーンテキスト
中身を書き換える → ポインタレシーバ
構造体が大きい(コピーが重い) → ポインタレシーバ
それ以外 → 値レシーバもう1つ、実務で効く決まりがあります。1つの型でレシーバを混ぜないことです。
Go
func (i Item) Label() string // 値
func (i *Item) MarkSoldOut() // ポインタ動きますが、読む人が毎回どちらか確かめることになります。1つでも書き換えるメソッドがあるなら、その型のメソッドは全部ポインタレシーバに揃えるのが慣習です。
nil に注意
ポインタは「何も指していない」状態になれます。
Go
var i *Item // nil
i.MarkSoldOut() // panic: invalid memory address値レシーバでは起こりえない失敗です。ポインタを使う代償として、nil かどうかを気にする場面が増えます。第8章で JSON を読み込むときに、この形が出てきます。
要件
Sellは在庫を1つ減らす。在庫が0のときは何もしない- 在庫が0になったら
SoldOutを true にする - 書き換えが反映されるレシーバを使う
- 売切れなら
商品名 売切れ、それ以外は商品名 在庫N点を返す
入出力例
sellOut("きのこ図鑑", 3, 1) → "きのこ図鑑 在庫2点"
sellOut("きのこ図鑑", 3, 3) → "きのこ図鑑 売切れ"
sellOut("古い辞書", 1, 5) → "古い辞書 売切れ"
sellOut("中古スマホ", 2, 0) → "中古スマホ 在庫2点"ヒント
編集 ゆめさく編集部