ポインタレシーバの場合
- 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
}
値レシーバに対してポインターレシーバで呼び出す場合
- メソッドは実行できない
まとめ
値レシーバであれば、初期値が格納されるため構造体のフィールドにアクセスすることが可能であるが、 逆を言えば意図せずに動作することもある。
確実にハンドリングしたい場合は、ポインターレシーバで定義した方が安全。