レシーバーの定義の違いによる挙動の変化

golangで、structにメソッドを定義するときに、レシーバー(funcとメソッド名の間の(hoge TypeName)的なところのこと)を定義します。

この定義の違いでどのように挙動が変わるのかを以下のサンプルコードで説明します。

サンプルコード中にコメントで示している型名でレシーバーを定義している方(notPointerMethod)を利用したあとのPrintlnではメソッド内で3を代入していますが実際には1が出力されます。


///型名でレシーバーを定義
func (t receiverSampleType) notPointerMethod(value int) {
    t.value = value
}

これは、メソッド実行時にmain側で宣言しているインスタンスとは別にreceiverSampleTypeがコピーされて渡される(値渡し)ため、別の実体に代入しているためです。
このようなレシーバーの定義でメソッドを宣言するときは、副作用を期待しても実際には別の実体に作用されるため副作用のないメソッド定義のときのみにしましょう。

逆にポインタ付きでレシーバーを定義している方(PointerMethod)は利用したあとの出力が3になります。


///ポインタ付きでレシーバーを定義
func (t *receiverSampleType) PointerMethod(value int) {
    t.value = value
}

これは、main側で宣言しているインスタンスの参照が渡される(参照渡し)ため、同じ実体に代入されるためです。
このようなレシーバーの定義でメソッドを宣言すると、メソッド内での操作がそのまま呼び元のインスタンスに作用するため、副作用を期待するときはこちらのレシーバー定義にしましょう。
逆に、副作用を期待したくないときは、やめておいたほうが安全かもしれません。


package main

import (
    "fmt"
)

type receiverSampleType struct {
    value int
}

func main() {
    instance := receiverSampleType{}

    instance.value = 1
    instance.notPointerMethod(3)
    fmt.Println(instance) // output : 1
    instance.PointerMethod(3)
    fmt.Println(instance) // output : 3

}

///型名でレシーバーを定義
func (t receiverSampleType) notPointerMethod(value int) {
    t.value = value
}

///ポインタ付きでレシーバーを定義
func (t *receiverSampleType) PointerMethod(value int) {
    t.value = value
}

Share