Go7 Advent Calendar 2019 の 20 日目の記事です。
昨日見たらぽっかり空いていたので埋めます。
皆さんは値とポインタを適切に使い分けることができているでしょうか?
基礎的なところなのでちゃんと理解しておきたいものですね。
私はたまに使うと細かなところを時々忘れていて、毎回資料探しからやり直すことになって面倒でした。
そこで、わかりやすかったページのリンクをここにまとめておくことにしました。
公式ドキュメントの翻訳も載せていますので、よろしければブックマークしてお使いください。
そもそもポインタがわからない方
まずは基礎全般を理解するのが良いと思います。
入門用のサイトや書籍で学びましょう。
(クリックで折り畳みを開閉)
公式資料
公式のドキュメントがやはり基本ですね。
公式 Wiki: Receiver Type の部分
(クリックで翻訳文を開閉)
> レシーバの型(⇒ [原文](https://github.com/golang/go/wiki/CodeReviewComments#receiver-type))
>
> メソッドのレシーバに値とポインタのどちらを使うか選ぶのは難しいものです。新米の Go プログラマには特に難しいです。もし迷ったらポインタを使いましょう。しかし、値レシーバのほうが適していることもあります。その理由としてよくあるのは効率性で、例えば変更することのない小さな構造体や基本型の値なら値レシーバのほうが効率的です。ガイドラインとして役立つものをいくつか挙げます。
>
> ---
> * レシーバがマップか関数かチャネルなら、ポインタを使わないこと。
> レシーバがスライスで、そのメソッドが再スライスや再アロケートをしないなら、ポインタを使わないこと。
> * メソッドでレシーバが変更されるなら、そのレシーバはポインタにすること。
> * レシーバが構造体で、`sync.Mutex` かそれに近い同期するフィールドを含んでいるなら、コピーを避けるためにレシーバをポインタにすること。
> * レシーバが大きな構造体か配列なら、ポインタレシーバのほうが効率的です。「大きな」とはどれくらいのことでしょうか。その要素全てを引数としてメソッドに渡すと考えたときに大きすぎると感じるなら、レシーバとしても大きすぎます。
> * 関数かメソッドが並行実行されるとき、あるいはそのメソッドから呼び出されるときに、レシーバが変更されることはありますか?
> 値型の場合、メソッドが呼ばれるとレシーバのコピーが作られるため、メソッドの外で起こった更新はそのレシーバに適用されません。
> 変更を元のレシーバにも適用する必要があれば、そのレシーバをポインタにすること。
> * レシーバが構造体か配列かスライスで、その要素が変化し得るものを指すポインタであれば、ポインタレシーバのほうがコードの読み手にも意図が明確になるので好ましいです。
> * レシーバが性質的に値型である小さな配列か構造体(例えば `time.Time` 型のようなもの)であって、フィールドがイミュータブルでポインタも持たないもの、あるいは `int` や `string` のようなシンプルな基本型であれば、値レシーバが適しています。
> 値レシーバなら、生じ得るガーベジの量を減らすことができます。
> もし値メソッドに値が渡されると、ヒープへの割り当てではなくスタックへのコピーが行われるからです。
> (ただし、コンパイラはこのヒープの割り当てを避けようとはするものの、うまくいくとは限りません。)
> プロファイルもしないままこの理由で値レシーバ型を選ばないこと。
>
> 最後に。もし迷ったらポインタレシーバを使いましょう。
Effective Go: Pointers vs. Values の部分
(クリックで翻訳文を開閉)
> ポインタ vs 値(⇒ [原文](https://golang.org/doc/effective_go.html#pointers_vs_values))
>
> [ByteSize の例](https://golang.org/doc/effective_go.html#constants) で見たように、名前の付いたどんな型(ポインタ型と interface 型は除く)にもメソッドを定義することができます。
> レシーバは構造体である必要はありません。
>
> 上述のスライスに関する議論において、[Append](https://golang.org/doc/effective_go.html#slices) という関数について書きました。
> それを関数としてではなくスライスが持つメソッドとしても定義できます。
> そのためには、メソッドと紐付けることができるものとして名前付きの型をまず宣言しておき、その型の値をメソッドのレシーバとして使います。
>
> ```go
> type ByteSlice []byte
>
> func (slice ByteSlice) Append(data []byte) []byte {
> // ここの中身は上記のAppendの関数とまったく同じ
> }
> ```
>
> このようにしても、更新されたスライスをメソッドから返さなければならない点は変わりません。
> その使いにくさを解消するには、ByteSlice のポインタをレシーバとして取るようにメソッドを再定義し、呼び出し元のスライスをメソッドで上書きできるようにします。
>
> ```go
> func (p *ByteSlice) Append(data []byte) {
> slice := *p
> // ここの中身は上記と同様だがreturnは無し
> *p = slice
> }
> ```
>
> 実は更に改善できます。
> 上の関数を、次のように標準の `Write` メソッドと同じ見た目となるよう変更します。
>
> ```go
> func (p *ByteSlice) Write(data []byte) (n int, err error) {
> slice := *p
> // ここも上記と同様
> *p = slice
> return len(data), nil
> }
> ```
>
> そうすると `*ByteSlice` 型は標準の `io.Writer` インタフェースを満たして便利になります。
> 例えば、その型に print して書き込むことができます。
>
> ```go
> var b ByteSlice
> fmt.Fprintf(&b, "This hour has %d days\n", 7)
> ```
>
> ByteSlice のアドレスを渡しているのは `*ByteSlice` だけが `io.Writer` を満たすからです。
> 値メソッドはポインタでも値でも呼び出せるのに対し、ポインタメソッドはポインタでしか呼び出せないというのが、レシーバの「ポインタ vs 値」のルールです。[^pointer_method]
>
> このルールは、ポインタメソッドならレシーバを変更できることによるものです。
> 値で呼び出したメソッドでは値のコピーを受け取るため、変更しても全てなかったことになります。
> そのため、このような誤用は言語によって許容されていません。
> しかし例外として便利なルールがあります。
> 値でポインタメソッドを呼び出すというよくあるケースにおいて、その値がアドレス可能であれば言語がアドレス演算子(&)を自動的に差し込んでケアしてくれます。
> 上の例では、b はアドレス可能な変数なので、単に `b.Write` という形で `Write` メソッドを呼び出すことができます。
コンパイラがそれを `(&b).Write` に書き換えてくれます。
>
> ちなみに、bytes のスライスで Write を使えるという考えは `bytes.Buffer` の実装の根幹となっています。
[^pointer_method]: [Go言語でinterfaceをimpleしてるつもりが「does not implement (method has pointer receiver)」って叱られる【golang】【pointer】【ダックタイピング】 - DRYな備忘録](http://otiai10.hatenablog.com/entry/2014/05/27/223556)
ポインタレシーバのメソッドにはポインタを使うルールはこちらの記事でもわかりやすいです。
引数・レシーバ等での使い分け
スライス
構造体
構造体に関するテクニック
ポインタのポインタ
パフォーマンス、安全性など
理解度チェック
理解できたと思ったら試してみてください。2