Goでは、メソッドは値型(value)およびポインタ型(pointer)で宣言できます。この機能は、データが誰によって変更されるかを明示的に制御するために初期のバージョンから存在しています。古典的な問題は、値のセマンティクス(コピー、変更しない)とポインタ(データへの共有アクセスと変更可能性)との間の距離の必要性です。
問題 — 値受信者でメソッドを宣言して期待した効果が得られないことや、ポインタ変数で値メソッドを呼び出すことによるミスを犯しやすい。
解決策 — 次のルールに従うこと。
コード例:
type Counter struct { Value int } func (c Counter) IncCopy() { c.Value++ } // 値受信者 func (c *Counter) IncPointer() { c.Value++ } // ポインタ受信者 c := Counter{} c.IncCopy() // Valueは0のまま c.IncPointer() // Valueは1になる
重要な特徴:
ポインタで値受信者メソッドを呼び出すことができますか?また、値でポインタメソッドを呼び出すことができますか?
Goは「裏で」ポインタを自動的にデリファレンスしたり、アドレスを取得したりするため、型が互換性がある場合に呼び出しが許可されます。しかし、すべての場合においてそうではありません—インターフェースでは予測通りに動作しないことがあります。
var c Counter (&c).IncCopy() // ポインタを通じて値メソッドを呼び出すことができる c.IncPointer() // ポインタメソッドを呼び出すことができ、Goは自動的にアドレスを取得する
構造体がポインタメソッドのみを実装している場合、その値をインターフェースに渡すとどうなりますか?
そのようなオブジェクトはポインタメソッドを要求するため、インターフェースを実装していないため、panicやコンパイルエラーが発生する可能性があります。
type D interface { IncPointer() } func f(d D) {} c := Counter{} f(c) // エラー!Counterは値としてインターフェースを実装していない f(&c) // 正しい
ポインタ受信者のメソッドを呼び出す際にポインタのコピーを渡した場合、構造体は変わりますか?
はい、ポインタがコピーされても、同じオブジェクトがその下にあるため、結果は同じになります。
c := Counter{} p := &c p2 := p p2.IncPointer() // Valueが増加する
エンジニアが値受信者メソッド「Update」を持つ構造体を実装します。インターフェースを通じて構造体が渡されますが、変更が「失われ」ます — それはコピーで作業しているからです。
利点:
欠点:
チーム内で明示的な合意:状態を変更するメソッドはすべてポインタ受信者であり、インターフェースはポインタでのみ実装され、値は「拡張」やユーティリティのためのものです。
利点:
欠点: