🐁

【Go言語】配列の存在チェックには len() != 0 を使う

2023/04/30に公開

はじめに

調べれば出てくると思いますが調べるのがめんどくさいので自分用のメモです。

バージョン

go version go1.20.3 darwin/arm64

配列の存在チェックの挙動

nil []string{}
==nil true true
len() = 0 false true
package main_test

import (
	"testing"
)

func IsNil(slice []string) bool {
	return slice == nil
}

func TestIsNil(t *testing.T) {
	t.Parallel()

	type args struct {
		slice []string
	}

	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "nil",
			args: args{slice: nil},
			want: true,
		},
		{
			name: "empty",
			args: args{slice: []string{}},
			want: false,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			got := IsNil(tt.args.slice)
			t.Log(got)
			if got != tt.want {
				t.Errorf("IsNil() = %v, want %v", got, tt.want)
			}
		})
	}
}

func IsEmpty(slice []string) bool {
	return len(slice) == 0
}

func TestIsEmpty(t *testing.T) {
	t.Parallel()

	type args struct {
		slice []string
	}

	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "nil",
			args: args{slice: nil},
			want: true,
		},
		{
			name: "empty",
			args: args{slice: []string{}},
			want: true,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			if got := IsEmpty(tt.args.slice); got != tt.want {
				t.Errorf("IsEmpty() = %v, want %v", got, tt.want)
			}
		})
	}
}

どういうときに困るのか

sliceをnilチェックしているような場合、決め内で先頭の要素を取り出そうとするとpanicが発生します。

package main

func main() {
	First([]string{})
}

func First(slice []string) string {
	if slice == nil {
		return ""
	}

	return slice[0]
}
go run main.go
panic: runtime error: index out of range [0] with length 0

goroutine 1 [running]:
main.First(...)
        main.go:12
main.main()
        main.go:4 +0x24
exit status 2

len()でチェックしていればpanicは発生しません。

package main

func main() {
	First([]string{})
}

func First(slice []string) string {
	if len(slice) == 0 {
		return ""
	}

	return slice[0]
}

ちなみにfor rangeを実行する場合はnilでチェックしようがlen()でチェックしようが問題なく動作します。

package main

import "fmt"

func main() {
	Print([]string{})
}

func Print(slice []string) {
	if slice == nil {
		fmt.Println("nil slice")

		return
	}

	for i, v := range slice {
		fmt.Printf("i: %d, v: %s\n", i, v)
	}
}
go run main.go

nilチェックした場合は何も出力される終了します。

一方、len()でチェックした場合はempty sliceと出力されて(早期リターンし)終了します。

package main

import "fmt"

func main() {
	Print([]string{})
}

func Print(slice []string) {
	if len(slice) == 0 {
		fmt.Println("empty slice")

		return
	}

	for i, v := range slice {
		fmt.Printf("i: %d, v: %s\n", i, v)
	}
}
go run main.go
empty slice

おわりに

たまに挙動を忘れてしまうので確認のために記事として書きました。
とりあえずlen()でチェックしておけば大丈夫ということです。

Discussion