It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【Go】メソッドとレシーバ

※ この記事時点のGoのバージョンは「1.10.2」です

Goでは、特定の型に関連づけられた関数を「メソッド」と呼びます。
また、メソッドを呼び出される対象(型)のことを「レシーバ」と呼びます。

inon29.hateblo.jp

ポインタレシーバと変数レシーバ(値レシーバ)

  • メソッドを宣言した時のレシーバの型が「ポインタ」なのか「変数(値)」なのかによってメソッドの挙動が異なります。
  • レシーバがポインタのものを「ポインタレシーバ」変数(値)のものを「変数レシーバ(値レシーバ)」と呼びます。
package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    fmt.Printf("X: %f, Y: %f\n", d.X, d.Y)
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    fmt.Printf("X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    d.Display()

    d2 := &Direction{20.0, 50.0}
    d2.DisplayP()
}

ポインタレシーバと変数レシーバの違い

  • 変数レシーバは、メソッド呼び出し時に変数の値をそのままコピーし、新たな値をメソッドに渡します。そのためメソッド内で変数を変更してももとの値は変更されません。
  • ポインタレシーバは、もとの値のポインタを渡すため、メソッド内で変数の値を変更するともとの値が変わります。
    Java(や他の言語)のメソッドの「値渡し」 「参照渡し」と同じです
package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    // xとyを書き換える
    d.X = 15.0
    d.Y = 5.0
    fmt.Printf("Display() => X: %f, Y: %f\n", d.X, d.Y)
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    // xとyを書き換える
    d.X = 99.0
    d.Y = 88.0
    fmt.Printf("DisplayP() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    d.Display()
    // 元の変数の値は変わらない
    fmt.Printf("main() d => X: %f, Y: %f\n", d.X, d.Y)

    d2 := &Direction{20.0, 50.0}
    d2.DisplayP()
    // 元の変数の値も変わる
    fmt.Printf("main() d2 => X: %f, Y: %f\n", d2.X, d2.Y)
}

出力

Display() => X: 15.000000, Y: 5.000000
main() d => X: 10.000000, Y: 3.500000
DisplayP() => X: 99.000000, Y: 88.000000
main() d2 => X: 99.000000, Y: 88.000000

レシーバの暗黙的型変換

宣言されているレシーバがポインタレシーバである時は、呼び出し元が「変数(値)」または「ポインタ」のどちらであっても実行できます。
変数であったとしてもGoのコンパイラが内部でポインタ型に変換して実行してくれます。

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    fmt.Printf("DisplayP() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    // ポインタレシーバのメソッドを変数(値)から呼び出せる
    d.DisplayP()
    // コンパイラが(&d)に変換してくれる
    (&d).DisplayP()
}

逆に宣言されているレシーバが変数レシーバ(値レシーバ)である時も、呼び出し元が「変数(値)」または「ポインタ」のどちらであっても実行できます。
この場合はGoのコンパイラが内部で変数に変換してくれています。
※ 変数レシーバであることは変わらないので実行時に渡されるのは呼び出しの変数のコピーになる

package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    fmt.Printf("Display() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := &Direction{20.0, 50.0}
    d.Display()
    // コンパイラが(*d)に変換してくれる
    (*d).Display()
}

構造体がポインタで合っても、変数であっても変わらずドット(.)アクセスできるのと同じように、利便性を考えこのような変換が行われるようです。

参照