Code Fix

上級

値レシーバのメソッドは呼び出し元の値を変更しない

値レシーバで定義したメソッドは、レシーバのコピーに対して操作を行います。メソッド内でフィールドを変更しても、呼び出し元の元の変数には反映されません。

なぜエラーが出ないのか

出力: 0
(エラーなし)
Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: 0
実際の出力 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  package main
 2  
 3  import "fmt"
 4  
 5  type Counter struct{ N int }
 6  
 7  func (c Counter  ) Inc() { c.N++ }
                ^
 8  
 9  func main() {
10  	c := Counter{N: 0}
11  	c.Inc()
12  	fmt.Println(c.N)
13  }
出力: 0

値レシーバのIncはcのコピーに対してN++を行うため、呼び出し元のcは変更されません。ポインタレシーバ(*Counter)にする必要があります。

直し方: Counter*Counter にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  package main
 2  
 3  import "fmt"
 4  
 5  type Wallet struct{ Balance int }
 6  
 7  func (w Wallet  ) Deposit() { w.Balance += 10 }
                ^
 8  
 9  func main() {
10  	w := Wallet{Balance: 100}
11  	w.Deposit()
12  	fmt.Println(w.Balance)
13  }
出力: 100

Depositが値レシーバだと、メソッド内で加算した結果は呼び出し元のwに反映されず、常に元の値のままです。

直し方: Wallet*Wallet にします。

この問題を解いてみる →

パターン3

 1  package main
 2  
 3  import "fmt"
 4  
 5  type Score struct{ Points int }
 6  
 7  func (s Score  ) Add() { s.Points += 5 }
               ^
 8  
 9  func main() {
10  	s := Score{Points: 0}
11  	s.Add()
12  	fmt.Println(s.Points)
13  }
出力: 0

メソッド呼び出しのたびに構造体全体がコピーされるため、値レシーバでは呼び出し元に変更が伝わりません。

直し方: Score*Score にします。

この問題を解いてみる →

パターン4

 1  package main
 2  
 3  import "fmt"
 4  
 5  type Tank struct{ Fuel int }
 6  
 7  func (t Tank  ) Fill() { t.Fuel += 30 }
               ^
 8  
 9  func main() {
10  	t := Tank{Fuel: 20}
11  	t.Fill()
12  	fmt.Println(t.Fuel)
13  }
出力: 20

Fillの中でt.Fuelを増やしても、それは複製されたコピーへの変更であり、元のtには影響しません。

直し方: Tank*Tank にします。

この問題を解いてみる →

パターン5

 1  package main
 2  
 3  import "fmt"
 4  
 5  type Stock struct{ Qty int }
 6  
 7  func (s Stock  ) Restock() { s.Qty += 2 }
               ^
 8  
 9  func main() {
10  	s := Stock{Qty: 3}
11  	s.Restock()
12  	fmt.Println(s.Qty)
13  }
出力: 3

変更したいなら明示的にポインタレシーバ(*Stock)にする必要があり、値レシーバのままでは何度呼んでも変化しません。

直し方: Stock*Stock にします。

この問題を解いてみる →

よくある誤解

「メソッド内でフィールドをいじれば、呼び出し元の値も自動的に更新されるはず」という思い込みは誤りです。値レシーバはメソッド呼び出しのたびに構造体全体をコピーするため、変更したいなら明示的にポインタレシーバ(*T)にする必要があります。

まとめ

値レシーバのメソッドは呼び出し元の値を変更しないは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)