🚧

[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