今川館

都内勤務の地味OLです

Goのnilのmapやスライスで許されない操作

Goのmapやスライスを操作するコードを書いていて、やたらとレビュー指摘をもらったのでここにダメな操作と大丈夫な操作をメモしておく。

例えば、こういうコードは余計なことをしているので直しなさいという指摘をもらった。

package main

import "fmt"

func main() {
	m := map[string][]int{}

	xs, ok := m["foo"]
	if !ok {
		xs = []int{} // nilのスライスが取れるから初期化した方がいいんじゃないか・・?
	}
	xs = append(xs, 10)
	m["foo"] = xs

	fmt.Println(m)
}

これは、こう書けるらしい。

package main

import "fmt"

func main() {
	m := map[string][]int{}
	m["foo"] = append(m["foo"], 10)
	fmt.Println(m)
}

結論から言うと、nilのmapにキー渡して値を入れようとするとpanicになるのでダメ。これ以外は大丈夫。

操作 ok/NG 結果
nilのmapをlenに渡す
例: len(m)
ok 0を返す
nilのmapをrangeで回す
例: for k, v := range m { ... }
ok 一度もループが回らない
nilのmapからdeleteで要素を消す
例: delete(m, "foo")
ok 特に何も起きない
nilのmapにキー渡して値を入れる
例: m["foo"] = 1
NG panicになる
(panic: assignment to entry in nil map)
nilのスライスをlenやcapに渡す
例: len(xs), cap(xs)
ok 0を返す
nilのスライスをrangeで回す
例: or _, x := range xs { ... }
ok 一度もループが回らない
nilのスライスをappendで書き換える
例: xs = append(xs, 1)
ok 要素を追加できる

Playgroundで試したサンプルコード
https://play.golang.org/p/Kje5KtMOMOw

package main

import (
	"fmt"
)

func main() {
	var (
		m  map[string][]int = nil
		xs []int            = nil
	)

	fmt.Println("***** len, cap *****", len(xs), cap(xs)) // これは大丈夫: ***** len, cap ***** 0 0
	for _, x := range xs {
		fmt.Println("*****", x) // 1回も通らない: でもrangeに渡されてもエラーにはならない
	}

	fmt.Println("===== len =====", len(m)) // これも大丈夫: ===== len ===== 0
	for k, v := range m {
		fmt.Println("=====", k, v) // こっちも1回も通らない: エラーにはならない
	}

	delete(m, "par?") // nilのmapをdeleteに渡してもpanicにならない

	/*

		m["foo"] = []int{1, 2, 3} // これはダメ: panic: assignment to entry in nil map
		fmt.Println("m->", m)

	*/

	xs = append(xs, 10)     // これは通る
	fmt.Println("xs->", xs) // xs-> [10]
}