ゆくゆくは有へと

おかゆ/オカ∃/大鹿有生/彼ノ∅有生 の雑記

【golang】レシーバの実引数は値/ポインタを問わないっぽい

まえがき

Pythonのデータサイエンス寄りのところを再復習してて、飽きてきたので他の言語やりたくなった。

F#, Rust, Nim.......。あげく一周回ってGOをやろう。からの疑問点。

本題

まずは引用を。

golang.jp

レシーバとしてポインタまたは値のどちらを受け取るかによって、次の違いがあります。レシーバとして値を受け取るメソッドでは、ポインタまたは値で呼び出すことができますが、ポインタを受け取るメソッドでは、ポインタでしか呼び出すことができません。これは後者ではメソッド内でレシーバを変更可能であるためです。(複製された値に加えた変更は破棄されてしまうため。)

とあるんですが、go 1.10.3 現在だとそうじゃないっぽい・・・?

package main

import (
    "fmt"
)

func main() {
    p := &Grid{5, 2}
    q := Grid{1, 0}
    fmt.Println(p, q)
    p.move(-2, 2)
    q.move(3, 3)
    fmt.Println(p, q)
}

type Grid struct {x, y int}

func (p *Grid) move(x, y int) *Grid{
    p.x += x
    p.y += y
    return p
}

結果がこうなる:

&{5 2} {1 0}
&{3 4} {4 3}

予想:

qGrid構造体のポインタではなく値を束縛してあるので、レシーバにGridのポインタ型(*Grid)を指定しているmoveメソッドが弾かれると予想していた。

実際:

通ったし、ちゃんと破壊的変更されちゃってる。

ええ~~~~~~~~???

もしかして、値で渡しても関数定義の型に従ってポインタが渡されるようになってるのか…?余計なお世話では…?

と思いきや、こちら(関数形式)で渡すとちゃんとエラー吐くことが分かりました。

func main() {
    p := &Grid{5, 2}
    q := Grid{1, 0}
    fmt.Println(p, q)
    p.move(-2, 2)
    (*Grid).move(q, 3, 3)  // ここが変更点
    fmt.Println(p, q)
}
# command-line-arguments
.\main.go:13:14: cannot use q (type Grid) as type *Grid in argument
to (*Grid).move

Grid*Gridじゃないからダメ!!!!!!」ってキレてくる。さっきは優しかったのに!

というわけで、値で渡してもそのポインタを自動的に渡してくれる…という挙動はレシーバ限定っぽい。

考察:なんでそんな仕様なんや

思うに、メソッドチェーンを考えてのことかなと(こなみかん)