【Golang】ポインタと実体の使いどころ

Golangにおけるポインタの使いどころ、実体の使いどころを整理しました。

# 結論

|  | イミュータブル変数 | 関数引数/メソッドレシーバ | ゼロNULL区別 |
|:-:|:-:|:-:|:-:|
| ポインタ | × | ◎ | ○ |
| 実体 | ○ | ○ | × |

# 1. イミュータブル変数は「実体」で宣言する。
ポインタは、別変数に値を渡すと、その変数からも元値の更新が可能となる。
つまり、複数の変数間で１つの値を共有する**ミュータブルな**変数を意味する。

```go
package main

import "fmt"

func main() {
	v1 := &S{Number: 1} // ポインタで宣言
	v2 := v1            // 別変数に値を渡す
	v2.Number = 10      // 別変数を更新
	fmt.Printf("v1: %v, v2: %v", v1, v2)
    // -> v1: &{10}, v2: &{10}
    // v1の値も更新されてしまう: ミュータブル変数
}

type S struct {
	Number int
}
```

一方で、実体を別変数に渡した場合、元の実体からコピーされた別の値が生成される。
よって、値を管理するのはただ1つの変数だけとなる**イミュータブル変数**を意味する。

```go
package main

import "fmt"

func main() {
	v1 := S{Number: 1}  // 実体で宣言
	v2 := v1            // 別変数に値を渡す
	v2.Number = 10      // 別変数を更新
	fmt.Printf("v1: %v, v2: %v", v1, v2)
    // -> v1: &{1}, v2: &{10}
    // v1の値は更新されない: イミュータブル変数
}

type S struct {
	Number int
}
```

# 2. パフォーマンスを向上させるには関数の引数またはメソッドのレシーバを「ポインタ」で宣言する。

関数を呼び出す際、**引数は元の値をコピーして受け取る**ため、ポインタであればアドレス値(64ビットCPUであれば8バイト分の値)を引数として読み込むことになる。

```go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	s := &S{Number: 10000000, Name: "someone", Address: []byte("abcdefg")}
    // ポインタを引数に渡す
	SizeChecker(s)
    // -> Param Size: 8 bytes
}

type S struct {
	Number  int
	Name    string
	Address []byte
}

func SizeChecker(s *S) { // ポインタを引数として受け取る
	fmt.Printf("Param Size: %v bytes", unsafe.Sizeof(s))
}
```

一方で、実体を引数に渡す場合、実体のデータそのものをコピーするため、(フィード数の多い構造体など)大きなデータ構造を持つ実体はコピーに必要なメモリ領域が大きくなり、パフォーマンス劣化の原因となる。

```go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	s := S{Number: 10000000, Name: "someone", Address: []byte("abcdefg")}
    // 実体を引数に渡す
	SizeChecker(s)
    // -> Param Size: 48 bytes
    // ポインタを渡す場合よりもコピーコストが増加: パフォーマンス劣化の原因
}

type S struct {
	Number  int
	Name    string
	Address []byte
}

func SizeChecker(s S) { // 実体を引数として受け取る
	fmt.Printf("Param Size: %v bytes", unsafe.Sizeof(s))
}
```

よって、関数のパフォーマンス向上を目的とした場合、**引数はポインタで渡す**ほうが望ましい。

なお、構造体のメソッドも同様に、レシーバは**ポインタで宣言**したほうがパフォーマンスを向上できる。

```go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	s := S{Number: 10000000, Name: "someone", Address: []byte("abcdefg")}
	s.PointerSizeCheckMethod() // -> Param Size: 8 bytes
	s.DataSizeCheckMethod()    // -> Param Size: 48 bytes
}

type S struct {
	Number  int
	Name    string
	Address []byte
}

func (s *S) PointerSizeCheckMethod() { // レシーバをポインタで受け取る
	fmt.Printf("Param Size: %v bytes\n", unsafe.Sizeof(s))
}

func (s S) DataSizeCheckMethod() { // レシーバを実体で受け取る
	fmt.Printf("Param Size: %v bytes\n", unsafe.Sizeof(s))
}
```

# 3. ゼロ値とNULL値の区別が必要なデータは「ポインタ」で宣言する。

ポインタで初期値を指定せずに変数宣言すると、初期値には\<nil>が格納される。
これは**アドレス値が存在しないこと**(=**NULL**)を意味する。

```go
package main

import "fmt"

func main() {
	var id *int      // ポインタで宣言
	var name *string // ポインタで宣言
	fmt.Printf("Init Value: id=%v, name=%v", id, name)
	// -> Init Value: id=<nil>, name=<nil>
	// ポインタの初期値は<nil>になる
}
```

一方、実体で初期値を指定せずに変数宣言した場合、初期値には各型で決められた値が格納される。
特に、**int型では初期値にゼロが格納**されるため、**ゼロとNULLを区別できない**。

```go
package main

import "fmt"

func main() {
	var id int      // 実体で宣言
	var name string // 実体で宣言
	fmt.Printf("Init Value: id=%v, name=%v", id, name)
	// -> Init Value: id=0, name=
	// 実体の初期値は型ごとに異なる
    // (int型: 初期値はゼロ, string型: 初期値は空文字)
}
```

よって、ゼロ値とNULL値を明確に区別したい時には、**ポインタでの変数宣言**が必要となる。

# 参考文献
[技術評論社「Software Design (ソフトウェアデザイン) 2021年1月号」2020年12月18日](https://www.amazon.co.jp/Software-Design-%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3-2021%E5%B9%B41%E6%9C%88%E5%8F%B7-%E9%9B%91%E8%AA%8C-ebook/dp/B08Q82B3Y5)