0
0

【Golang】ポインターレシーバ、値レシーバがnilである場合の挙動の違いについて

Last updated at Posted at 2023-10-28

ポインタレシーバの場合

  • nilポインタをレシーバとしてメソッドを呼び出すことは可能
  • メソッド内で 「nilを判定」 することが可能
  • ただし、メソッド内で 「ポインタのフィールドにアクセス」 しようとすると、パニック(ランタイムエラー)が発生する。
package main

import "fmt"

type MyStruct struct{}

func (ms *MyStruct) MyMethod() {
	// nil判定
	if ms == nil {
		fmt.Println("レシーバはnilです")
	} else {
		fmt.Println("レシーバはnilではありません")
	}
}

func main() {
	var myVar *MyStruct // ポインターの初期化 nil
	myVar.MyMethod()    // 結果:レシーバはnilです
}

フィールドにアクセスしようとする場合、panicが発生する

package main

import "fmt"

type MyStruct struct {
	Value int
}

func (ms *MyStruct) MyMethod() {
	fmt.Println(ms.Value) // nilの構造体のフィールドにアクセスするためパニックが発生する
}

func main() {
	var myVar *MyStruct // ポインターの初期値 nil
	myVar.MyMethod()    // 結果:panic: runtime error: invalid memory address or nil pointer dereference
}

ポインターレシーバの場合

  • nilの判定が可能

ポインターレシーバの場合

  • nilの構造体のフィールドのアクセスはパニックが発生する

値レシーバの場合

  • nilを値レシーバとして使用すると、初期化された数値がレシーバに格納されメソッドが実行される。
  • 初期値が入っているため、nil判定は不可。ビルドエラーとなる。
package main

import "fmt"

type MyStruct struct{}

func (ms MyStruct) MyMethod() {
	fmt.Println(ms)
}

func main() {
	var myVar MyStruct // 実体の初期値
	myVar.MyMethod()   // 結果:{}
}

nil判定しようとする場合、ビルドエラーとなる

package main

import "fmt"

type MyStruct struct{}

func (ms MyStruct) MyMethod() {
	if ms == nil {
		fmt.Println("レシーバはnilです")
	} else {
		fmt.Println("レシーバはnilではありません")
	}
}

func main() {
	var myVar MyStruct // 実体の初期値
	myVar.MyMethod()   // 結果:mismatched types MyStruct and untyped nil
}

フィールドにアクセスしようとする場合、初期値が返却される

package main

import "fmt"

type MyStruct struct {
	Value int
}

func (ms MyStruct) MyMethod() {
	fmt.Println(ms.Value)
}

func main() {
	var myVar MyStruct // 実体の初期値
	myVar.MyMethod()   // 結果:0
}

値レシーバの場合

  • 構造体及び構造体のフィールドへのアクセスは、初期値が入っているため可能

値レシーバの場合

  • 初期値が入っているため、 「nil判定」は型違いによりビルドエラーが発生する

ポインターレシーバでメソッド定義、値レシーバでメソッド呼び出しの場合

  • ポインターレシーバのメソッドを値レシーバで呼び出すことは可能
  • Golangのランタイムは、値をポインターレシーバで期待される形式に自動的に変換する
package main

import "fmt"

type MyStruct struct{}

func (ms *MyStruct) MyMethod() {
	// nil判定
	if ms == nil {
		fmt.Println("レシーバはnilです")
	} else {
		fmt.Println(ms)
	}
}

func main() {
	var myVar MyStruct // 実体の初期値
	myVar.MyMethod()   // 結果:&{}
}

値レシーバを定義した際に初期値が入るため、nilとは判定されない。
結果、else文が実行される。

構造体のフィールドにアクセスする場合

package main

import "fmt"

type MyStruct struct {
	Value int
}

func (ms *MyStruct) MyMethod() {
	fmt.Println(ms.Value)
}

func main() {
	var myVar MyStruct // 実体の初期値
	myVar.MyMethod()   // 結果:0
}

値レシーバを定義した際に初期値が入るため、初期値のフィールドを出力する。

ポインターレシーバに対して値レシーバで呼び出す場合

  • 初期値が入るためnilと判定されない。
  • 初期値が入るため構造体のフィールドへのアクセスでエラーが発生しない。
  • Golangのランタイムは、値をポインターレシーバで期待される形式に自動的に変換する

値レシーバでメソッド定義、ポインターレシーバでメソッド呼び出しの場合

package main

import "fmt"

type MyStruct struct{}

func (ms MyStruct) MyMethod() {
	fmt.Println(ms)
}

func main() {
	var myVar *MyStruct // ポインターの初期値 nil
	myVar.MyMethod()    // 結果:invalid memory address or nil pointer dereference
}

nil判定しようとする場合、ビルドエラーとなる

package main

import "fmt"

type MyStruct struct{}

func (ms MyStruct) MyMethod() {
	if ms == nil {
		fmt.Println("レシーバはnilです")
	} else {
		fmt.Println("レシーバはnilではありません")
	}
}

func main() {
	var myVar *MyStruct // ポインターの初期値 nil
	myVar.MyMethod()    // 結果:mismatched types MyStruct and untyped nil
}

フィールドにアクセスしようとする場合、panicが発生する

package main

import "fmt"

type MyStruct struct {
	Value int
}

func (ms MyStruct) MyMethod() {
	fmt.Println(ms.Value)
}

func main() {
	var myVar *MyStruct // ポインターの初期値 nil
	myVar.MyMethod()    // 結果:invalid memory address or nil pointer dereference
}

値レシーバに対してポインターレシーバで呼び出す場合

  • メソッドは実行できない

まとめ

値レシーバであれば、初期値が格納されるため構造体のフィールドにアクセスすることが可能であるが、 逆を言えば意図せずに動作することもある。
確実にハンドリングしたい場合は、ポインターレシーバで定義した方が安全。

0
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
0
0