Go言語

【Go言語】メソッドのレシーバーとnil

Go言語のメソッドには

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

の2つがある。

値レシーバ

値レシーバは、メソッド呼び出し時に変数の値をコピーし、新たな値をメソッドに渡す。
そのためメソッド内で変数を変更してももとの値は変更されない。

次のように、「名」を更新するメソッドを値レシーバで定義しても呼び出し元の値は更新されない。

type User struct {
    FirstName string
    LastName  string
}

func (u User) FullName() string {
    return u.LastName + " " + u.FirstName
}

func (u User) UpdateFirstName(firstName string) {
    u.FirstName = firstName
}

func main() {
    user := User{
        FirstName: "太郎",
        LastName:  "田中",
    }
    fmt.Println(user.FullName()) // 田中 太郎
    user.UpdateFirstName("次郎")
    fmt.Println(user.FullName()) // 田中 太郎
}

 

ポインタレシーバ

ポインタレシーバの場合、もとの値のポインタを渡す(値のコピーはしない)ためメソッド内で変数の値を変更すると、もとの値も変更される。

type User struct {
    FirstName string
    LastName  string
}

func (u *User) FullName() string {
    if u == nil {
        return ""
    }

    return u.LastName + " " + u.FirstName
}

func (u *User) UpdateFirstName(firstName string) {
    u.FirstName = firstName
}

func main() {
    user := User{
        FirstName: "太郎",
        LastName:  "田中",
    }
    // コンパイラが暗黙的に、(&user).FullName()としてくれている
    fmt.Println(user.FullName()) // 田中 太郎
    user.UpdateFirstName("次郎")
    fmt.Println(user.FullName()) // 田中 次郎
}

 

nilもレシーバになり得る

nilに対してメソッドを呼び出そうとするとエラーになるかと思いきや、必ずしもそうではありません。

マップやスライス、ポインタなど、nilがその型で意味を持つゼロ値の場合はnilもレシーバになり得る。

マップの例

マップのゼロ値はnilなので、レシーバにnilが来ても許される。
例えば次のようなメソッド呼び出しをしてもエラーにならない。

type ValuesMap map[string]int

func (m ValuesMap) Keys() []string {
    keys := make([]string, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }

    return keys
}

func main() {
    var m ValuesMap
    fmt.Println(m.Keys()) // []
}

ポインタレシーバの場合

変数 T のポインタは *T 型で、ゼロ値は nil 。

なので、ポインタレシーバの場合もレシーバにnilがきても許される。

type User struct {
    FirstName string
    LastName  string
}

func (u *User) FullName() string {
    if u == nil {
        return ""
    }

    return u.LastName + " " + u.FirstName
}

func (u *User) UpdateFirstName(firstName string) {
    u.FirstName = firstName
}

func main() {
    u1 := User{
        FirstName: "太郎",
        LastName:  "田中",
    }
    fmt.Println(u1.FullName()) // 田中 太郎
    var u2 User
    fmt.Println(u2.FullName()) // ""
}

※ レシーバがnilでもメソッド呼び出しはできるが、フィールド呼び出しをしたときにエラーになるので、nil判定をしている。

 

 

-Go言語

© 2021 フリエン生活 Powered by AFFINGER5