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


## ポインタレシーバの場合
* nilポインタをレシーバとしてメソッドを呼び出すことは可能
* メソッド内で **「nilを判定」** することが可能
* ただし、メソッド内で **「ポインタのフィールドにアクセス」** しようとすると、パニック（ランタイムエラー）が発生する。
```go
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です
}

```
https://go.dev/play/p/f-A8fgqRPrh

#### フィールドにアクセスしようとする場合、panicが発生する
```go
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
}

```
https://go.dev/play/p/gBrd3qNcdc7

:::note info
ポインターレシーバの場合
* nilの判定が可能
:::
:::note alert
ポインターレシーバの場合
* nilの構造体のフィールドのアクセスはパニックが発生する
:::


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

```go
package main

import "fmt"

type MyStruct struct{}

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

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

https://go.dev/play/p/hAhIGYMVyaN

#### nil判定しようとする場合、ビルドエラーとなる
```go
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
}
```
https://go.dev/play/p/wVsqnBaBm7M


#### フィールドにアクセスしようとする場合、初期値が返却される
```go
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
}
```
https://go.dev/play/p/tWDNpBqCvdR

:::note info
値レシーバの場合
* 構造体及び構造体のフィールドへのアクセスは、初期値が入っているため可能
:::
:::note alert
値レシーバの場合
* 初期値が入っているため、 **「nil判定」は型違いによりビルドエラーが発生する**
:::

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

```go
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文が実行される。

https://go.dev/play/p/IsDS-xLG1SM

#### 構造体のフィールドにアクセスする場合
```go
package main

import "fmt"

type MyStruct struct {
	Value int
}

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

func main() {
	var myVar MyStruct //　実体の初期値
	myVar.MyMethod()   // 結果：０
}
```
値レシーバを定義した際に初期値が入るため、初期値のフィールドを出力する。

https://go.dev/play/p/lHT-MlB69f7

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


## 値レシーバでメソッド定義、ポインターレシーバでメソッド呼び出しの場合
```go
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
}
```

https://go.dev/play/p/2BbSLcU4IFo

#### nil判定しようとする場合、ビルドエラーとなる
```go
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
}
```

https://go.dev/play/p/sdDYUhkABMh

#### フィールドにアクセスしようとする場合、panicが発生する
```go
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
}
```
https://go.dev/play/p/7oAVoqaRgUw

:::note alert
値レシーバに対してポインターレシーバで呼び出す場合
* メソッドは実行できない
:::

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