## 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軸のうち「型内統一」の話