[Golang] 値レシーバとポインタレシーバ
Go では、型にふるまい(メソッド)を追加できる。 メソッドを追加する先をレシーバと呼ぶ。 「レシーバにメソッドを追加する」など。
以下は、同じメソッドを異なる 2 つのレシーバに追加している。どう違う?
type User struct {
Name string
}
// 値レシーバ
func (u User) SetName(name string) {
u.Name = name
}
// ポインタレシーバ
func (u *User) SetName(name string) {
u.Name = name
}
値レシーバの使いどころ
- メソッドによってレシーバが指す変数を上書きできたら困るとき。Getter として使う場合。参照だけであることを明示できる
ポインタレシーバの使いどころ
- メソッド内でレシーバが指す変数を上書きする必要があるとき
- メソッドを呼び出すたびに変数がコピーされるとまずいとき。レシーバが巨大な変数である場合に有効
使い分けるときの注意点
ある型において、値レシーバとポインタレシーバを混在させないのがセオリー。 なぜなら、値レシーバとポインタレシーバは異なる interface の実装としてあつかわれるから。
たとえば、 (v *Vertex) Abs()
を実装しても (v Vertex) Abs()
を実装したことにはならない。
interface の実装として複数のメソッドを実装する必要があるとき、値レシーバとポインタレシーバのどちらか一方ですべてのメソッドを実装しないと、interface の実装になりえない。
基本的にはポインタレシーバを使い、レシーバが指す変数が上書きされないことを保証したいときだけ値レシーバを使うことが多い印象。
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
// MyFloatにはAbs()が実装されているので代入できる
a = f
// *VertexにはAbs()が実装されているので代入できる
a = &v
// VertexにはAbs()が実装されていないので代入できない
a = v // error: Vertex does not implement Abser
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
ref. A Tour of Go