メソッドの定義
メソッドは型に紐づく関数です。メソッドにはレシーバという特殊な引数が必要で、どの型の値に対して実行するかを指定します。
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 {
return r.W * r.H
}型に紐づくため、オブジェクト指向な言語とは異なり、構造体以外の型にもメソッドを定義することができます。
type Length float64
func (l Length) Double() Length {
return l * 2
}同じ型のメソッド集合内でレシーバ名は一意である必要があります。
値レシーバとポインタレシーバ
レシーバには値レシーバとポインタレシーバの二つがあります。値レシーバは、呼び出し元の値のコピーを受け取って実行するため、元の値に副作用がありません。ポインタレシーバは、呼び出し元の値へのポインタを受け取ってから実行するため、元の値を変更することができます。
func (r Rect) Area() float64 {
return r.W * r.H
}
// 呼び出し元の値を書き換えている
func (r *Rect) Scale(f float64) {
r.W *= f
r.H *= f
}値レシーバで定義されたメソッドは、値(Rect型)からしか呼び出すことができません。同様にポインタレシーバで定義されたメソッドは、ポインタ(*Rect型)からしか呼び出すことができません。しかし、コンパイル時に自動補完が走るため、どちらのケースでも変数から呼び出す形で問題なく動きます。
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (r *Rect) Scale(f float64) { r.W *= f; r.H *= f }
r := Rect{W: 3, H: 4}
r.Scale(2) // コンパイラが (&r).Scale(2) に読み替える
fmt.Println(r.Area())
p := &Rect{W: 3, H: 4}
p.Scale(2) // ポインタレシーバなのでそのまま呼べる
fmt.Println(p.Area()) // コンパイラが (*p).Area() に読み替えるただし、リテラル・関数の戻り値・mapのインデックス式など、アドレスが取得できない値の場合は自動補完が効かず、ポインタレシーバのメソッドを直接呼び出すことができません。
// Rect{W: 1, H: 1}.Scale(2) // NG: リテラルはアドレスが取得できない
(&Rect{W: 1, H: 1}).Scale(2) // OK: &Rect{...}でリテラルから*Rectを作れる(変数に入れないので変更は捨てられる)
rects := map[string]Rect{"a": {}}
// rects["a"].Scale(2) // NG: mapのインデックス式はアドレスが取得できない
v := rects["a"]
v.Scale(2)
rects["a"] = v // 変数に取り出して変更し、mapへ戻すポインタレシーバのメソッドは、レシーバがnilであっても呼び出し自体は可能です。ただし、メソッド内でフィールドにアクセスした時点で nilポインタデリファレンスが発生しパニックになります。一方、値レシーバのメソッドをnilのポインタから呼び出すと、コンパイラが値をコピーしようとして(*p)の展開を行うため、呼び出しの時点でnilポインタデリファレンスが発生しパニックになります。
var p *Rect
// p.Area() // 値レシーバ: 呼び出し時に(*p)で値コピー → nilポインタデリファレンスでパニック
// p.Scale(2) // ポインタレシーバ: 呼び出しは成功するが、内部でr.Wを参照 → nilポインタデリファレンスでパニックメソッド式とメソッド値
メソッドは関数値として扱えます。
r := Rect{W: 3, H: 4}
fn := r.Area // メソッド値: レシーバがあらかじめ結び付いている
fmt.Println(fn())
area := Rect.Area // メソッド式: レシーバを第1引数で渡す
fmt.Println(area(r))埋め込みとメソッド昇格
構造体に別の型を埋め込むと、その型のメソッドが昇格して外側の型から呼び出せるようになります。外側の型に同名メソッドがある場合は引数が異なる場合でも外側が優先されます。
type Engine struct{}
func (e Engine) Start() {}
type Car struct {
Engine
}
var c Car
c.Start() // Engine.Start が昇格