1. はじめに
Uber Go Style Guide: nil-is-a-valid-slice
Uberのスタイルガイドにある通り、sliceの空チェックは長さでチェックしなくてはいけません。
To check if a slice is empty, always use len(s) == 0. Do not check for nil.
Bad
func isEmpty(s []string) bool {
return s == nil
}
Good
func isEmpty(s []string) bool {
return len(s) == 0
}
さて、なぜsliceの空チェックで「nil」ではなく「長さ」でチェックするのか、体で覚えているだけできちんと理解していなかったので、まとめてみます。
2. sliceを「長さ」でチェックする理由
理由は、sliceが空になり、nilにならない場合が発生する可能性があるため、「長さ」でチェックしないと漏れが発生するためです。
ポイントは、
・sliceを空で宣言しても宣言の仕方によってnil sliceかempty sliceになる
・nil sliceとempty sliceともにlenとcapが0
ということです
2-1. 空で宣言しても宣言の仕方によってnil sliceかempty sliceになる
空で宣言しても、宣言の仕方によってはnil sliceかempty sliceと違いが出てきてしまい、nilチェックでは以下のパターンでチェックが漏れてしまいます。
func main() {
// nil slice
// こう定義するとnilになる
var nilSlice []int
fmt.Println(nilSlice, len(nilSlice), cap(nilSlice))
fmt.Printf("nilSlice: %s\n\n", isEmptyByNil(nilSlice))
// empty slice
// こう定義するとnilにはならず、lenとcapが0となる
emptySlice := []int{}
fmt.Println(emptySlice, len(emptySlice), cap(emptySlice))
fmt.Printf("emptySlice: %s\n\n", isEmptyByNil(emptySlice))
}
func isEmptyByNil(intSlice []int) string {
if intSlice == nil {
return "nil!"
} else {
return "not nil!"
}
}
// 実行結果
[] 0 0
nilSlice: nil!
[] 0 0
emptySlice: not nil!
2-2. nil sliceとempty sliceともにlenとcapが0
nil sliceとempty sliceともにlenとcapが0という性質を利用して、長さでチェックをすれば漏れが発生することはありません。
func main() {
// こう定義するとnilになる
var nilSlice []int
fmt.Println(nilSlice, len(nilSlice), cap(nilSlice))
fmt.Printf("nilSlice: %s\n\n", isEmptyByLen(nilSlice))
// こう定義するとnilにはならず、lenとcapが0のスライスとなる
emptySlice := []int{}
fmt.Println(emptySlice, len(emptySlice), cap(emptySlice))
fmt.Printf("emptySlice: %s\n\n", isEmptyByLen(emptySlice))
}
func isEmptyByLen(intSlice []int) string {
if len(intSlice) == 0 {
return "len is zero!"
} else {
return "len is not zero!"
}
}
// 実行結果
[] 0 0
nilSlice: len is zero!
[] 0 0
emptySlice: len is zero!
参考
Uber Go Style Guide: nil-is-a-valid-slice
技術メモ: Golangのnil sliceとnil map