## What — 2種類のレシーバー
社内リポのディスパッチャの Handler 構造体を読んでいて、`func (h *Handler) DispatchImport(...)` の `*` の意味が分からなかった。調べて分かったのは、`*` あり/なしは気分ではなく **「本体を変更できるか」「コピーコスト」「型内統一」の3軸** で決まっているということ。
| | 値レシーバー(`*`なし) | ポインタレシーバー(`*`あり) |
|---|---|---|
| 渡るもの | 構造体の **コピー** | 本体の **住所** |
| 中身を書き換えると | コピーが変わって捨てられる(本体は不変) | 本体に反映される |
| 呼び出しコスト | 毎回まるごと複製 | アドレス1個分だけ |
Counter で `count++` を3回する実験で確かめた。
- **値レシーバー**:メソッド内で見える内部アドレスが毎回変わる(呼ぶたびに複製されている証拠)。3回 `count++` しても本物の count は 0 のまま
- **ポインタレシーバー**:常に本体と同じアドレス。count はちゃんと 3 になる
逆に「`*` あり = どこかで変更している」は成り立たない。DispatchImport は h を読むだけなのに `*Handler` だ。`*` を付ける理由は変更だけでなく、**コピー代の節約と型内統一**([[Goは1つの型でレシーバーを混在させない]])もあるから。
## How — 書き換えが効く/効かないを見分ける
```go
type User struct {
Name string
Age int
}
func (u *User) Update() { u.Name = "hoge" } // *あり → 本体が変わる
func (u User) UpdateCopy() { u.Name = "hoge" } // *なし → コピーが変わって捨てられる
func main() {
u := &User{}
u.Update()
fmt.Printf("%+v\n", u) // &{Name:hoge Age:0}
u2 := &User{}
u2.UpdateCopy()
fmt.Printf("%+v\n", u2) // &{Name: Age:0} ← 変わらない
}
```
- 値で作った変数 `u := User{}` でも `u.Update()` は動く。Go が自動で `(&u).Update()` に変換してくれる(**変数のときだけ**)
> [!warning]
> **無言のバグの温床**:値レシーバーで中身を書き換えるコードはコンパイルが通ってしまう。「変更したはずなのに反映されない」ときは、まずレシーバーの `*` の有無を疑う。
おまけ:`%w` は `fmt.Errorf` 専用。`Printf` で使うと `%!w(...)` とエラー表示になる。構造体の中身を見たいときは `%+v`。
> [!success]
> 迷ったら3軸で考える:**変更するか? コピーが重いか? 型内で統一されているか?** 読むだけの型でもコピー代節約で `*` は普通にある。`*` の有無から「この型は変更される可能性があるか」を逆算できる。
## 関連
- [[Go]]
- [[Goは1つの型でレシーバーを混在させない]] — 3軸のうち「型内統一」の話