Goのスライスの定義で少しハマったこと

かなり初歩的な理解不足だったと思うのですが、忘れないために備忘録として残します。

## TL;DR
スライスの宣言だけと初期化は厳密には違い、nil sliceと空sliceの挙動が違うよという話

## 何をしてしまったのか
以下のようなことを実現するつもりでした。
- 特定のスライスとスライスを組み合わせて、一つの変数（hogeList)にしてレスポンスを返す
- 空だった場合は`{"List":[]}`でjsonで返却する
echoを使った簡単なイメージは以下の通りです。
```go
package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

type (
	Hoge struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}
	HogeResponse struct {
		HogeList []Hoge `json:"hogeList"`
	}
)

func main() {
	e := echo.New()
	e.GET("/api/hoge", func(c echo.Context) error {
		hogeList := HogeResponse{
			HogeList: []Hoge{},
		}

		return c.JSON(http.StatusOK, hogeList)
	})

	e.Logger.Fatal(e.Start(":8080"))
}
```
これはHogeResponseの変数を定義してHogeListも空として定義したので
想定通り空リストが返却されました。
```
{
    "hogeList": []
}
```
例えばこれにデータを入れて返すとします。
先ほどのコードを少しいじりました。
```go
package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

type (
	Hoge struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}
	HogeResponse struct {
		HogeList []Hoge `json:"hogeList"`
	}

	// 取得した加工したいデータとする
	FugaData struct {
		ID      int    `json:"id"`
		Name    string `json:"name"`
		Place   string `json:"place"`
		Address string `json:"address"`
	}
	FugaList []FugaData
)

func main() {
	e := echo.New()
	e.GET("/api/hoge", func(c echo.Context) error {
		hogeList := HogeResponse{
			HogeList: []Hoge{},
		}

		// データを取得したとします
		data := []FugaData{
			{ID: 1, Name: "hoge1", Place: "tokyo", Address: "tokyo"},
			{ID: 2, Name: "hoge2", Place: "longdon", Address: "longdon"},
			{ID: 3, Name: "hoge3", Place: "paris", Address: "paris"},
		}

		// データをhogeとして格納する
		var ret []Hoge
		for _, d := range data {
			ret = append(ret, Hoge{
				ID:   d.ID,
				Name: d.Name,
			})
		}
		// 格納したhogeを元のデータに格納する
		hogeList.HogeList = ret

		return c.JSON(http.StatusOK, hogeList)
	})

	e.Logger.Fatal(e.Start(":8080"))
}
```
これを動かすと以下のようにデータが取得できます。まあ普通ですね。
```
{
	"hogeList": [
		{
			"id": 1,
			"name": "hoge1"
		},
		{
			"id": 2,
			"name": "hoge2"
		},
		{
			"id": 3,
			"name": "hoge3"
		}
	]
}
```

で、問題はここでした。
ケースによっては、fugaデータがない場合があり、その場合は想定通り
Listは空のリストで返却したいです。
以下のように変更してみました。
```go
package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

type (
	Hoge struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}
	HogeResponse struct {
		HogeList []Hoge `json:"hogeList"`
	}

	// 取得した加工したいデータとする
	FugaData struct {
		ID      int    `json:"id"`
		Name    string `json:"name"`
		Place   string `json:"place"`
		Address string `json:"address"`
	}
	FugaList []FugaData
)

func main() {
	e := echo.New()
	e.GET("/api/hoge", func(c echo.Context) error {
		hogeList := HogeResponse{
			HogeList: []Hoge{},
		}

		// データはありません
		data := []FugaData{}

		// データをhogeとして格納する
		var ret []Hoge

		// 回すデータはないので、このループは空振ります
		for _, d := range data {
			ret = append(ret, Hoge{
				ID:   d.ID,
				Name: d.Name,
			})
		}
		// 格納したhogeを元のデータに格納する
		hogeList.HogeList = ret

		return c.JSON(http.StatusOK, hogeList)
	})

	e.Logger.Fatal(e.Start(":8080"))
}
```
とループが回らないように変更するとレスポンスが変わりました。
```
{
    "hogeList": null
}
```
おおぅ..と思いました。
てっきり、`var ret []Hoge`としていたので、このretは少なくともスライスが定義されていると
考えていました。
（完全にPHP脳でした）

どうやらスライスの変数は、宣言したか・初期化したかで挙動が変わってしまうようです。
今回のケースは宣言だけだったのでnil sliceとなっていたようでした。
空スライスに変更してみます。
```go
package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

type (
	Hoge struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}
	HogeResponse struct {
		HogeList []Hoge `json:"hogeList"`
	}

	// 取得した加工したいデータとする
	FugaData struct {
		ID      int    `json:"id"`
		Name    string `json:"name"`
		Place   string `json:"place"`
		Address string `json:"address"`
	}
	FugaList []FugaData
)

func main() {
	e := echo.New()
	e.GET("/api/hoge", func(c echo.Context) error {
		hogeList := HogeResponse{
			HogeList: []Hoge{},
		}

		// データはありません
		data := []FugaData{}

		// データをhogeとして格納する
		ret := []Hoge{} // 初期化に変更

		// 回すデータはないので、このループは空振ります
		for _, d := range data {
			ret = append(ret, Hoge{
				ID:   d.ID,
				Name: d.Name,
			})
		}
		// 格納したhogeを元のデータに格納する
		hogeList.HogeList = ret

		return c.JSON(http.StatusOK, hogeList)
	})

	e.Logger.Fatal(e.Start(":8080"))
}
```
```
{
    "hogeList": []
}
```
すごい初歩的なことですが、あまり意識して使ってなかったなと反省しました。

## 参考
https://scrapbox.io/jiroshin-knowledge/-Go-_nil_slice%E3%81%A8%E7%A9%BAslice%E3%81%AE%E9%81%95%E3%81%84