I am a newbee to Go programming. I have read in go programming book that slice consists of three things: a pointer to an array, length and capacity.
I am getting con
var s1 []int // nil slice
s2 := []int{} // non-nil, empty slice
s3 := make([]int, 0) // non-nil, empty slice
warning, if dealing with JSON, a nil slice will encode to null instead of [], which can break some (javascript) clients that try to iterate (without a null check) over null which isn't iterable.
A slice can be nil in a very literal sense:
var n []int
n == nil // true
That is the only easy case. The notion of "empty" slice is not well defined: A slice s
with len(s) == 0
is certainly empty, regardless of its capacity.
The most sensible thing to do is: Ignore the underlying implementation, one never needs to know how a slice is represented internally. All it matters is the defined behaviour of a slice.
How to test whether a slice is empty or not?
The most sensible definition of " slice s
is empty" is a slice containing no elements which translates to len(s) == 0
. This definition holds for nil as well as for non-nil slices.
Can anyone please tell whether nil and empty slices are same things? If they both are different, then please tell what is the difference between those two?
Technically a nil slice and a non-nil slice are different (one being == nil, the other being != nil), but this distinction typically does not matter as you can append
to a nil slice and len and cap on nil slices return 0
var n []int
len(n) == cap(n) == 0 // true
n = append(n, 123)
len(n) == 1 // true
Read about zero values in Go for further information. A nil slice is like a nil channel or a nil map: It is uninitialised. You initialise by either make
ing them or by a literal. As said above, there is no reason to think about the underlying representation.
Also, what value does the pointer holds in non-nil slices, whose length and capacity are zero?
This is an implementation detail which may vary from compiler to compiler or even from version to version. Nobody needs to know this to write correct and portable Go programs.
nil
and empty slices (with 0 capacity) are not the same, but their observable behavior is the same. By this I mean:
for range
over them (will be 0 iterations)See this simple example (a nil
slice and 2 non-nil
empty slices):
var s1 []int // nil slice
s2 := []int{} // non-nil, empty slice
s3 := make([]int, 0) // non-nil, empty slice
fmt.Println("s1", len(s1), cap(s1), s1 == nil, s1[:], s1[:] == nil)
fmt.Println("s2", len(s2), cap(s2), s2 == nil, s2[:], s2[:] == nil)
fmt.Println("s3", len(s3), cap(s3), s3 == nil, s3[:], s3[:] == nil)
for range s1 {}
for range s2 {}
for range s3 {}
Output (try it on the Go Playground):
s1 0 0 true [] true
s2 0 0 false [] false
s3 0 0 false [] false
(Note that slicing a nil
slice results in a nil
slice, slicing a non-nil
slice results in a non-nil
slice.)
You can only tell the difference by comparing the slice value to the predeclared identifier nil
, they behave the same in every other aspect.
To tell if a slice is empty, simply compare its length to 0
: len(s) == 0
. It doesn't matter if it's the nil
slice or a non-nil
slice, it also doesn't matter if it has a positive capacity; if it has no elements, it's empty.
s := make([]int, 0, 100)
fmt.Println("Empty:", len(s) == 0, ", but capacity:", cap(s))
Prints (try it on the Go Playground):
Empty: true , but capacity: 100
A slice value is represented by a struct defined in reflect.SliceHeader:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
In case of a nil
slice, this struct will have its zero value which is all its fields will be their zero value, that is: 0
.
Having a non-nil
slice with both capacity and length equal to 0
, Len
and Cap
fields will most certainly be 0
, but the Data
pointer may not be. It will not be, that is what differentiates it from the nil
slice. It will point to a zero-sized underlying array.
Note that the Go spec allows for values of different types having 0 size to have the same memory address. Spec: System considerations: Size and alignment guarantees:
A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.
Let's check this. For this we call the help of the unsafe package, and "obtain" the reflect.SliceHeader
struct "view" of our slice values:
var s1 []int
s2 := []int{}
s3 := make([]int, 0)
fmt.Printf("s1 (addr: %p): %+8v\n",
&s1, *(*reflect.SliceHeader)(unsafe.Pointer(&s1)))
fmt.Printf("s2 (addr: %p): %+8v\n",
&s2, *(*reflect.SliceHeader)(unsafe.Pointer(&s2)))
fmt.Printf("s3 (addr: %p): %+8v\n",
&s3, *(*reflect.SliceHeader)(unsafe.Pointer(&s3)))
Output (try it on the Go Playground):
s1 (addr: 0x1040a130): {Data: 0 Len: 0 Cap: 0}
s2 (addr: 0x1040a140): {Data: 1535812 Len: 0 Cap: 0}
s3 (addr: 0x1040a150): {Data: 1535812 Len: 0 Cap: 0}
What do we see?
nil
slice has 0
data pointers2
and s3
slices do have the same data pointer, sharing / pointing to the same 0-sized memory value