[golang] nil スライスと empty スライスの特徴と使い分けを理解する

# 目的
nil スライスと empty スライスの使い分けを理解する。

# 検証
```
~/praprago$ go version
go version go1.23.2 linux/amd64
```

``` main.go
package main

import "fmt"

// go run .
func main() {
	fmt.Println(" ============================== (1)")
	pra_slice_nil_1()
	fmt.Println(" ============================== (2)")
	pra_slice_nil_2()
}

```
``` pra_slice_nil.go
package main

import (
	"encoding/json"
	"fmt"
	"unsafe"
)

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (1)
// nil スライスと empty スライスの違いを示している。
// nil スライスは、メモリが割り当てがないので、特に理由がない限り、関数の返り値としては nil スライスを返すのがよい。
// 「nil スライス or empty スライス」の条件で判定したい場合は、len() で判定するのがよい。
//
//go:noinline
func pra_slice_nil_1() {
	var mySlice []string
	mySlice2 := []string(nil)
	mySlice3 := []string{}
	mySlice4 := make([]string, 0)

	if mySlice == nil {
		println("mySlice is nil")
	} else {
		println("mySlice is not nil")
	}
	if mySlice2 == nil {
		println("mySlice2 is nil")
	} else {
		println("mySlice2 is not nil")
	}
	if mySlice3 == nil {
		println("mySlice3 is nil")
	} else {
		println("mySlice3 is not nil")
	}
	if mySlice4 == nil {
		println("mySlice4 is nil")
	} else {
		println("mySlice4 is not nil")
	}

	if len(mySlice) == 0 {
		println("mySlice is empty")
	} else {
		println("mySlice is not empty")
	}
	if len(mySlice2) == 0 {
		println("mySlice2 is empty")
	} else {
		println("mySlice2 is not empty")
	}
	if len(mySlice3) == 0 {
		println("mySlice3 is empty")
	} else {
		println("mySlice3 is not empty")
	}
	if len(mySlice4) == 0 {
		println("mySlice4 is empty")
	} else {
		println("mySlice4 is not empty")
	}

	println("")
	fmt.Println(" ===== mySlice")
	printSliceDataInfo("mySlice", mySlice)
	fmt.Println(" ===== mySlice2")
	printSliceDataInfo("mySlice2", mySlice2)
	fmt.Println(" ===== mySlice3")
	printSliceDataInfo("mySlice3", mySlice3)
	fmt.Println(" ===== mySlice4")
	printSliceDataInfo("mySlice4", mySlice4)
}

// unsafe.SliceData は nil スライスの場合、nil(0x0) ポインタを返す。
// unsafe.SliceData は empty スライスの場合、未定義のポインタを返す。
//
//go:noinline
func printSliceDataInfo(name string, slice []string) {
	println(name, "length:", len(slice), "capacity:", cap(slice))
	dataPtr := unsafe.SliceData(slice)
	println(name, "SliceData pointer:", dataPtr)
	if dataPtr == nil {
		println(name, "has no memory allocated")
	} else {
		println(name, "has memory allocated")
	}
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (2)
// 普段は nil スライスと empty スライスの違いを意識する必要はあまりないが、json などの一部のパッケージで、明確に区別する場合があるので注意するべし。
// nil スライスは null になるが、empty スライスは [] になる。
type MyStruct struct {
	Name   string   `json:"name"`
	Values []string `json:"values"`
}

func pra_slice_nil_2() {
	struct1 := MyStruct{Name: "struct1", Values: nil}
	struct2 := MyStruct{Name: "struct2", Values: []string(nil)}
	struct3 := MyStruct{Name: "struct3", Values: []string{}}
	struct4 := MyStruct{Name: "struct4", Values: make([]string, 0)}

	jsonStruct1, _ := json.Marshal(struct1)
	jsonStruct2, _ := json.Marshal(struct2)
	jsonStruct3, _ := json.Marshal(struct3)
	jsonStruct4, _ := json.Marshal(struct4)

	println("struct1 JSON:", string(jsonStruct1))
	println("struct2 JSON:", string(jsonStruct2))
	println("struct3 JSON:", string(jsonStruct3))
	println("struct4 JSON:", string(jsonStruct4))
}

```
```
~/praprago$ go run .
 ============================== (1)
mySlice is nil
mySlice2 is nil
mySlice3 is not nil
mySlice4 is not nil
mySlice is empty
mySlice2 is empty
mySlice3 is empty
mySlice4 is empty

 ===== mySlice
mySlice length: 0 capacity: 0
mySlice SliceData pointer: 0x0
mySlice has no memory allocated
 ===== mySlice2
mySlice2 length: 0 capacity: 0
mySlice2 SliceData pointer: 0x0
mySlice2 has no memory allocated
 ===== mySlice3
mySlice3 length: 0 capacity: 0
mySlice3 SliceData pointer: 0xc00008eea0
mySlice3 has memory allocated
 ===== mySlice4
mySlice4 length: 0 capacity: 0
mySlice4 SliceData pointer: 0xc00008eea0
mySlice4 has memory allocated
 ============================== (2)
struct1 JSON: {"name":"struct1","values":null}
struct2 JSON: {"name":"struct2","values":null}
struct3 JSON: {"name":"struct3","values":[]}
struct4 JSON: {"name":"struct4","values":[]}
```
```
~/praprago$ go build -gcflags "-m=2" .
# github.com/XXX/praprago
./pra_slice_nil.go:79:6: cannot inline printSliceDataInfo: marked go:noinline
./pra_slice_nil.go:16:6: cannot inline pra_slice_nil_1: marked go:noinline
./pra_slice_nil.go:99:6: cannot inline pra_slice_nil_2: function too complex: cost 318 exceeds budget 80
./main.go:6:6: cannot inline main: function too complex: cost 274 exceeds budget 80
./main.go:7:13: inlining call to fmt.Println
./main.go:9:13: inlining call to fmt.Println
./pra_slice_nil.go:65:13: inlining call to fmt.Println
./pra_slice_nil.go:67:13: inlining call to fmt.Println
./pra_slice_nil.go:69:13: inlining call to fmt.Println
./pra_slice_nil.go:71:13: inlining call to fmt.Println
./pra_slice_nil.go:79:25: name does not escape
./pra_slice_nil.go:79:38: slice does not escape
./pra_slice_nil.go:71:14: " ===== mySlice4" escapes to heap:
./pra_slice_nil.go:71:14:   flow: {storage for ... argument} = &{storage for " ===== mySlice4"}:
./pra_slice_nil.go:71:14:     from " ===== mySlice4" (spill) at ./pra_slice_nil.go:71:14
./pra_slice_nil.go:71:14:     from ... argument (slice-literal-element) at ./pra_slice_nil.go:71:13
./pra_slice_nil.go:71:14:   flow: fmt.a = &{storage for ... argument}:
./pra_slice_nil.go:71:14:     from ... argument (spill) at ./pra_slice_nil.go:71:13
./pra_slice_nil.go:71:14:     from fmt.a := ... argument (assign-pair) at ./pra_slice_nil.go:71:13
./pra_slice_nil.go:71:14:   flow: {heap} = *fmt.a:
./pra_slice_nil.go:71:14:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./pra_slice_nil.go:71:13
./pra_slice_nil.go:69:14: " ===== mySlice3" escapes to heap:
./pra_slice_nil.go:69:14:   flow: {storage for ... argument} = &{storage for " ===== mySlice3"}:
./pra_slice_nil.go:69:14:     from " ===== mySlice3" (spill) at ./pra_slice_nil.go:69:14
./pra_slice_nil.go:69:14:     from ... argument (slice-literal-element) at ./pra_slice_nil.go:69:13
./pra_slice_nil.go:69:14:   flow: fmt.a = &{storage for ... argument}:
./pra_slice_nil.go:69:14:     from ... argument (spill) at ./pra_slice_nil.go:69:13
./pra_slice_nil.go:69:14:     from fmt.a := ... argument (assign-pair) at ./pra_slice_nil.go:69:13
./pra_slice_nil.go:69:14:   flow: {heap} = *fmt.a:
./pra_slice_nil.go:69:14:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./pra_slice_nil.go:69:13
./pra_slice_nil.go:67:14: " ===== mySlice2" escapes to heap:
./pra_slice_nil.go:67:14:   flow: {storage for ... argument} = &{storage for " ===== mySlice2"}:
./pra_slice_nil.go:67:14:     from " ===== mySlice2" (spill) at ./pra_slice_nil.go:67:14
./pra_slice_nil.go:67:14:     from ... argument (slice-literal-element) at ./pra_slice_nil.go:67:13
./pra_slice_nil.go:67:14:   flow: fmt.a = &{storage for ... argument}:
./pra_slice_nil.go:67:14:     from ... argument (spill) at ./pra_slice_nil.go:67:13
./pra_slice_nil.go:67:14:     from fmt.a := ... argument (assign-pair) at ./pra_slice_nil.go:67:13
./pra_slice_nil.go:67:14:   flow: {heap} = *fmt.a:
./pra_slice_nil.go:67:14:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./pra_slice_nil.go:67:13
./pra_slice_nil.go:65:14: " ===== mySlice" escapes to heap:
./pra_slice_nil.go:65:14:   flow: {storage for ... argument} = &{storage for " ===== mySlice"}:
./pra_slice_nil.go:65:14:     from " ===== mySlice" (spill) at ./pra_slice_nil.go:65:14
./pra_slice_nil.go:65:14:     from ... argument (slice-literal-element) at ./pra_slice_nil.go:65:13
./pra_slice_nil.go:65:14:   flow: fmt.a = &{storage for ... argument}:
./pra_slice_nil.go:65:14:     from ... argument (spill) at ./pra_slice_nil.go:65:13
./pra_slice_nil.go:65:14:     from fmt.a := ... argument (assign-pair) at ./pra_slice_nil.go:65:13
./pra_slice_nil.go:65:14:   flow: {heap} = *fmt.a:
./pra_slice_nil.go:65:14:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./pra_slice_nil.go:65:13
./pra_slice_nil.go:19:22: []string{} does not escape
./pra_slice_nil.go:20:18: make([]string, 0) does not escape
./pra_slice_nil.go:65:13: ... argument does not escape
./pra_slice_nil.go:65:14: " ===== mySlice" escapes to heap
./pra_slice_nil.go:67:13: ... argument does not escape
./pra_slice_nil.go:67:14: " ===== mySlice2" escapes to heap
./pra_slice_nil.go:69:13: ... argument does not escape
./pra_slice_nil.go:69:14: " ===== mySlice3" escapes to heap
./pra_slice_nil.go:71:13: ... argument does not escape
./pra_slice_nil.go:71:14: " ===== mySlice4" escapes to heap
./pra_slice_nil.go:105:33: struct1 escapes to heap:
./pra_slice_nil.go:105:33:   flow: {heap} = &{storage for struct1}:
./pra_slice_nil.go:105:33:     from struct1 (spill) at ./pra_slice_nil.go:105:33
./pra_slice_nil.go:105:33:     from json.Marshal(struct1) (call parameter) at ./pra_slice_nil.go:105:32
./pra_slice_nil.go:106:33: struct2 escapes to heap:
./pra_slice_nil.go:106:33:   flow: {heap} = &{storage for struct2}:
./pra_slice_nil.go:106:33:     from struct2 (spill) at ./pra_slice_nil.go:106:33
./pra_slice_nil.go:106:33:     from json.Marshal(struct2) (call parameter) at ./pra_slice_nil.go:106:32
./pra_slice_nil.go:107:33: struct3 escapes to heap:
./pra_slice_nil.go:107:33:   flow: {heap} = &{storage for struct3}:
./pra_slice_nil.go:107:33:     from struct3 (spill) at ./pra_slice_nil.go:107:33
./pra_slice_nil.go:107:33:     from json.Marshal(struct3) (call parameter) at ./pra_slice_nil.go:107:32
./pra_slice_nil.go:108:33: struct4 escapes to heap:
./pra_slice_nil.go:108:33:   flow: {heap} = &{storage for struct4}:
./pra_slice_nil.go:108:33:     from struct4 (spill) at ./pra_slice_nil.go:108:33
./pra_slice_nil.go:108:33:     from json.Marshal(struct4) (call parameter) at ./pra_slice_nil.go:108:32
./pra_slice_nil.go:103:51: make([]string, 0) escapes to heap:
./pra_slice_nil.go:103:51:   flow: struct4 = &{storage for make([]string, 0)}:
./pra_slice_nil.go:103:51:     from make([]string, 0) (spill) at ./pra_slice_nil.go:103:51
./pra_slice_nil.go:103:51:     from MyStruct{...} (struct literal element) at ./pra_slice_nil.go:103:21
./pra_slice_nil.go:103:51:     from struct4 := MyStruct{...} (assign) at ./pra_slice_nil.go:103:10
./pra_slice_nil.go:103:51:   flow: {storage for struct4} = struct4:
./pra_slice_nil.go:103:51:     from struct4 (interface-converted) at ./pra_slice_nil.go:108:33
./pra_slice_nil.go:102:55: []string{} escapes to heap:
./pra_slice_nil.go:102:55:   flow: struct3 = &{storage for []string{}}:
./pra_slice_nil.go:102:55:     from []string{} (spill) at ./pra_slice_nil.go:102:55
./pra_slice_nil.go:102:55:     from MyStruct{...} (struct literal element) at ./pra_slice_nil.go:102:21
./pra_slice_nil.go:102:55:     from struct3 := MyStruct{...} (assign) at ./pra_slice_nil.go:102:10
./pra_slice_nil.go:102:55:   flow: {storage for struct3} = struct3:
./pra_slice_nil.go:102:55:     from struct3 (interface-converted) at ./pra_slice_nil.go:107:33
./pra_slice_nil.go:102:55: []string{} escapes to heap
./pra_slice_nil.go:103:51: make([]string, 0) escapes to heap
./pra_slice_nil.go:105:33: struct1 escapes to heap
./pra_slice_nil.go:106:33: struct2 escapes to heap
./pra_slice_nil.go:107:33: struct3 escapes to heap
./pra_slice_nil.go:108:33: struct4 escapes to heap
./pra_slice_nil.go:110:34: string(jsonStruct1) does not escape
./pra_slice_nil.go:111:34: string(jsonStruct2) does not escape
./pra_slice_nil.go:112:34: string(jsonStruct3) does not escape
./pra_slice_nil.go:113:34: string(jsonStruct4) does not escape
./main.go:9:14: " ============================== (2)" escapes to heap:
./main.go:9:14:   flow: {storage for ... argument} = &{storage for " ============================== (2)"}:
./main.go:9:14:     from " ============================== (2)" (spill) at ./main.go:9:14
./main.go:9:14:     from ... argument (slice-literal-element) at ./main.go:9:13
./main.go:9:14:   flow: fmt.a = &{storage for ... argument}:
./main.go:9:14:     from ... argument (spill) at ./main.go:9:13
./main.go:9:14:     from fmt.a := ... argument (assign-pair) at ./main.go:9:13
./main.go:9:14:   flow: {heap} = *fmt.a:
./main.go:9:14:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./main.go:9:13
./main.go:7:14: " ============================== (1)" escapes to heap:
./main.go:7:14:   flow: {storage for ... argument} = &{storage for " ============================== (1)"}:
./main.go:7:14:     from " ============================== (1)" (spill) at ./main.go:7:14
./main.go:7:14:     from ... argument (slice-literal-element) at ./main.go:7:13
./main.go:7:14:   flow: fmt.a = &{storage for ... argument}:
./main.go:7:14:     from ... argument (spill) at ./main.go:7:13
./main.go:7:14:     from fmt.a := ... argument (assign-pair) at ./main.go:7:13
./main.go:7:14:   flow: {heap} = *fmt.a:
./main.go:7:14:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./main.go:7:13
./main.go:7:13: ... argument does not escape
./main.go:7:14: " ============================== (1)" escapes to heap
./main.go:9:13: ... argument does not escape
./main.go:9:14: " ============================== (2)" escapes to heap
```

# おまけ
`[]string(nil)` という初期化は、`var mySlice []string`と違い、下記のように書けるのがいいですね。
```
mySlice := append([]string(nil), "Hello")
```

# 参考

https://www.amazon.co.jp/-/en/Teiva-Harsanyi/dp/4295017531

https://stackoverflow.com/questions/25543520/declare-slice-or-make-slice
> Just found a difference. If you use
>
> `var list []MyObjects`
> and then you encode the output as JSON, you get null.
>
> But using make:
> `list := make([]MyObjects, 0)`
> results in [] as expected.

https://medium.com/@habibridho/golang-nil-vs-empty-slice-87fd51c0a4d
> Golang’s encoding/json encodes Nil Slice to null which can be unacceptable if our API contract defines Data as a not null, array of string.

https://qiita.com/Yashy/items/1d4f277998866b186e19
> 理由は、sliceが空になり、nilにならない場合が発生する可能性があるため、「長さ」でチェックしないと漏れが発生するためです。

https://pkg.go.dev/unsafe#SliceData
> `func SliceData(slice []ArbitraryType) *ArbitraryType`
> SliceData returns a pointer to the underlying array of the argument slice.
> - If cap(slice) > 0, SliceData returns &slice[:1][0].
> - If slice == nil, SliceData returns nil.
> - Otherwise, SliceData returns a non-nil pointer to an unspecified memory address.

最後の説明は、`mySlice3 := []string{}` や `mySlice4 := make([]string, 0)` によって初期化された場合の話ですね。
この場合、nil(0) ポインタではなく、未定義のポインタを返します。

https://pkg.go.dev/reflect#SliceHeader
> Deprecated: Use unsafe.Slice or unsafe.SliceData instead.