技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

type、メソッド、レシーバー【Go】

メソッド

type定義した型に対する関数は、このように書くことができますが、

type Vertex struct {
    X, Y int
}

func Area(v Vertex) int {
    return v.X * v.Y
}

次のように書くことで、オブジェクト指向ライクに表現できます。

type Vertex struct {
    X, Y int
}

func (v Vertex) Area() int {
    return v.X * v.Y
}

これをメソッドと言います。
goにはclassがないため、あくまでもオブジェクト指向「ライク」ではありますが、
これによって、変数名を呼び出し元で入力すると、メソッド名が推測補完されるようになります。
定義したtypeとメソッドが紐づけられた状態です。

func main() {
    v := Vertex{5, 2}
    fmt.Println(v.Area())    // vとArea()が紐づいている
}


レシーバー

メソッドと関連づけるstructについて、
値にする場合は、値レシーバー、
ポインタとする場合、ポインタレシーバーと呼ばれます。

func (v Vertex) Area() int {    // vは値レシーバー
    return v.X * v.Y
}

func (v *Vertex) Scale(rate int) {    // vはポインタレシーバー
    if v == nil {    // ポインタレシーバのメソッドは、 nil ポインタ変数からでも呼び出しが可能なため、panicを引き起こさないよう回避する
        return
    }
    v.X *= rate
    v.Y *= rate
}

typeの中身を変更する場合には、ポインタレシーバーである必要があります。
値レシーバーだと、コピーが作成され、コピーに対して処理されるからです。
(returnで値を返す(中身を変更しない)場合、値レシーバーの方が良いこともあります。参考資料1番目を参照。) また、ポインタレシーバのメソッドは、 nil ポインタ変数からでも呼び出しが可能なため、panicを引き起こさないようif文で回避するべきです。
レシーバーに渡すことができるのは、パッケージ内でtype定義された型のみです。
typeとは、型や型リテラルに別名をつけることができる、予約語です。

typeで別名をつけられる型は次の通りです。

  • 組み込み型(intやfloat64)
  • パッケージ内の型(typeで定義されたもの)
  • パッケージ外の型(typeで定義され、パッケージ名を指定して記述するもの)
  • リテラル
  • 関数


つまりは、次のようなこともできます。

type Height int    // int型に別名Heightをつける

func (h *Height) backHeight(x Height){
    if h == nil {    // ポインタレシーバのメソッドは、 nil ポインタ変数からでも呼び出しが可能なため、panicを引き起こさないよう回避する
        return
    }
    *h = *h * x
}

func main() {
    var h Height = 2    // Height型として宣言
    fmt.Printf("%T %v\n", h, h)    // main.Height 2
    h.backHeight(h)
    fmt.Println(h)    // 4

    w := 4
    fmt.Printf("%T %v\n", w, w)    // int 4  Heightとして宣言しなければ、通常通りの型(この場合はint)
}


関数を使うと、こんなこともできます。

type Func func()

func (f Func) doubleF(){
    f()
    f()
}

func main() {
    num := 1
    var f Func = func(){
        num++
    }
    f.doubleF()    // fが2度実行される
    fmt.Println(num)    // 3
}


さらに次のように、引数をとることもできます。

type Func func(x int)    //引数を持つ関数型を別名Funcと定義

func (f Func) doubleF(x int){    // このメソッド実行時に引数を指定する
    f(x)
    f(x)
}

func main() {
    num := 1
    var f Func = func(x int){    // 引数の定義を、typeで定義したFuncと合わせる
        num += x
    }
    f.doubleF(3)    // 引数に3を指定。fが2度実行される
    fmt.Println(num)    // 7
}


type定義の補足

typeを定義してプログラムを実行すると、

comment on exported type ~ should be of the form "~ ..." (with optional leading article)

と注意が表示されているかもしれません。
その時は、このように、

// Func is ~
type Func func()

コメントアウトして、そのtypeの説明をすれば解消されます。

参考

skatsuta.github.io

github.com

qiita.com