はじめに
Goのレシーバーって何?って思った人に向けて、Goで使われるレシーバーという概念と、その中で重要な値レシーバーとポインタレシーバーの2種類の違いと使い分けについて解説します。
Goの構造体について
まず前提として、Goでは構造体を使って複数のデータをひとまとめにできます。
構造体は次のように定義します。
type Person struct {
Name string
Age int
}
構造体のインスタンスは以下のように、初期化することができます。
person := Person{Name: "taro", Age: 42}
レシーバーとは何か?
レシーバーとは、構造体に関連付けられた関数(メソッド)を定義するための機能です。
これにより、構造体のインスタンスに対して操作を行うことができます。
以下にコード例を示します。
// 構造体の定義
type Person struct {
Name string
Age int
}
// Person構造体にGreetというメソッドを定義する
func (p Person) Greet() {
fmt.Printf("Hello, I'm %s, %d years old.\n", p.Name, p.Age)
}
func main() {
person := Person{Name: "Taro", Age: 42}
person.Greet() // Hello I'm Taro, 42 years old.
}
この例では、Person
構造体にGreet
メソッドを追加しています。
func (p Person) Greet()
の部分で、p Person
がレシーバーとなっています。
これにより、Person
型の変数(オブジェクト)からGreet
を呼び出すことができます。
要するに、レシーバーを使うことで、構造体に関連する関数を定義することができるのです。
値レシーバーとポインタレシーバー
レシーバーには、「値レシーバー」と「ポインタレシーバー」の2種類があります。
- 値レシーバー:構造体のコピーを受け取る
- ポインタレシーバー:構造体のポインタ(参照)を受け取る
構造体の値をメソッド内で変更したい場合、ポインタレシーバーを使う必要があります。
それでは、具体的な例を見ていきましょう。
ポインタレシーバーの使用例
次の例では、Person
構造体にBirthday
メソッドを追加しています。
このメソッドはポインタレシーバーを使用しているため、構造体の値を変更することができます。
func (p *Person) Birthday() {
p.Age++ // ポインタレシーバーを使っているので、構造体の値を変更できる
}
func main() {
person := Person{Name: "Taro", Age: 42}
person.Birthday()
person.Greet() // Hello, I'm Taro, 43 years old.
}
値レシーバーの使用例
func (p Person) Birthday() {
p.Age++ // 値レシーバーを使っているので、呼び出し元の構造体は変更されない
}
func main() {
person := Person{Name: "Taro", Age: 42}
person.Birthday() // 値が変わらない
person.Greet() // Hello, I'm Taro, 42 years old.
}
値レシーバーを使用した場合は、構造体の中身が変更できないことに注意しましょう。
値レシーバーとポイントレシーバーの使い分け
使い分けについては、使用例で確認したとおり、構造体の値を変更するかどうかによって判断することができます。
それ以外にも、構造体が大きい場合は、効率性の観点から、ポインタレシーバーを使用するといった選択方法があります。
GoのWikiに記載されていた値レシーバーとポインタレシーバーの使い分けを表にまとめました。
状況 | ポインタレシーバー | 値レシーバー |
---|---|---|
レシーバーがmap, func, chan | × | 〇 |
レシーバーがスライスでリスライス・再割当てがない | × | 〇 |
レシーバーが小さい配列や構造体でポインタを持たない(プロファイリングを行った上で判断する) | × | 〇 |
メソッドがレシーバーを変更する必要がある | 〇 | × |
レシーバーが変更される可能性がある | 〇 | × |
レシーバーが大きな構造体や配列、同期フィールドを含む | 〇 | × |
基本的には、同じ型で値レシーバーとポインタレシーバーを混在しないことにも気をつけましょう。もし迷った場合は、ポインタレシーバーを使うことが推奨されています。
詳細については、以下のリンクをご覧ください。
https://github.com/golang/go/wiki/CodeReviewComments#receiver-type
まとめ
Goのレシーバーとは、構造体に関連付けられた関数(メソッド)を定義するための機能でした。値レシーバーとポインタレシーバーを状況に応じて上手に使いこなしましょう!
参考