5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Golangの'nil slice'と'non-nil slice'で地味に詰まった話

Last updated at Posted at 2020-09-15

Golangでテストコードを書いていた際に、地味に詰まったことがあったので共有。

サンプルコード

intのスライスを引数として受け取り、その中から正の整数だけを返す関数をサンプルとして用意しました。

example.go
package example

// Example は、テスト用に作成した正の整数だけを返す関数
func Example(nums []int) (ret []int) {
	for _, num := range nums {
		if num > 0 {
			ret = append(ret, num)
		}
	}
	return
}

この関数に対して、下記の様なテストコードを書きます。
(本当はもっとテストケースを用意するべきだと思います。)

example_test.go
package example

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestExample(t *testing.T) {

	tests := []struct {
		input    []int
		expected []int
	}{
		{
			input:    []int{1, -2, 3},
			expected: []int{1, 3},
		},
                {
			input:    []int{-1, -2, -3},
			expected: []int{},
		},
	}

	for _, test := range tests {
		result := Example(test.input)
		assert.Equal(t, result, test.expected)
	}
}

そして、このテストを実行してみると、下記のログが表示されテストは失敗します。

$ go test --run TestExample

--- FAIL: TestExample (0.00s)
    example_test.go:27: 
                Error Trace:    example_test.go:27
                Error:          Not equal: 
                                expected: []int(nil)
                                actual  : []int{}
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1,2 +1,3 @@
                                -([]int) <nil>
                                +([]int) {
                                +}
                                 
                Test:           TestExample
FAIL
exit status 1

どうやら、
期待している値(expected)が、([]int) <nil>になっているのに対して、
実際に返される値(result)は、([]int) {}となっているため、テストが失敗しているようです。

nil sliceとnon-nil slice

このテスト失敗の理由を説明するためには、lengthとcapacityが共に0のスライスとして、
nil slicenon-nil sliceというものが存在することを説明する必要があります。

この2つのスライスは、見た目上ほとんど同じ挙動なのですが、
その名の通りnilかnilでないかという違いがあります。

package main

import "fmt"

func main() {
    // nil sliceになる
    var nSlice []int

    // non-nil sliceになる
    eSlice  := []int{}
    eSlice2 := make([]int, 0)

    fmt.Println(len(nSlice),  cap(nSlice),  nSlice  == nil)
    fmt.Println(len(eSlice),  cap(eSlice),  eSlice  == nil)
    fmt.Println(len(eSlice2), cap(eSlice2), eSlice2 == nil)
}
出力
0 0 true
0 0 false
0 0 false

テストの失敗の原因

Goの名前付き戻り値にsliceを指定して、何も値を代入しないままreturnした場合、nil sliceを返します。

サンプルコード
package main

import (
	"fmt"
)

func main() {
  slice := example()
  fmt.Println(len(slice),  cap(slice),  slice == nil)
}

func example () (slice []int) {
  return
}

最初にお見せしたテストでは、example関数はnil sliceを返すにもかかわらず、
non-nil sliceと比較するようなテストになってしまっていたため、テストが失敗してしまったのです。

シンプルなミスであるにもかかわらず、
nil sliceとnon-nil sliceのことを自分があまり意識していなかったために、
原因に気づくまでに少し時間がかかってしまいました。反省です。

まとめ

  • lengthとcapacityが共に0であるsliceには、nilとnon-nilの2種類がある。
  • 名前付き戻り値で、sliceをそのままreturnすると、nil sliceになる。
5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?