LoginSignup
3
0

More than 1 year has passed since last update.

【Go言語入門】Goのレシーバーとは?2種類のレシーバーを使い分ける方法

Last updated at Posted at 2023-04-02

はじめに

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のレシーバーとは、構造体に関連付けられた関数(メソッド)を定義するための機能でした。値レシーバーとポインタレシーバーを状況に応じて上手に使いこなしましょう!

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0