Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nil slices vs non-nil slices vs empty slices in Go language

Tags:

slice

go

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 confused between nil slices(slice has no underlying array to point to, len = 0,cap=0), non-nil slices where only len = 0,cap = 0 and empty 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?

How to test whether a slice is empty or not?

Also, what value does the pointer holds in non-nil slices, whose length and capacity are zero?

like image 425
Surbhi Vyas Avatar asked Jun 01 '17 10:06

Surbhi Vyas


People also ask

Is an empty slice nil Golang?

In Golang, when a variable is declared without an initialization value, its value will be set to the type's zero value. The zero value of a Slice is nil, so in our example above, when we declare var foo []string the value of foo is actually nil not an empty slice of string [] .

Is nil the same as empty?

What's the difference? For the most part, there's actually no observable difference between nil slices and empty slices. The built-in functions append , len , and cap all work in the same way for both, and you can for... range over either with the same result (0 iterations).

What is a nil slice?

A nil slice has a length and capacity of 0 and has no underlying array.

How do you empty a slice in Go?

Setting the slice to nil is the best way to clear a slice. nil slices in go are perfectly well behaved and setting the slice to nil will release the underlying memory to the garbage collector. Note that slices can easily be aliased so that two slices point to the same underlying memory.


1 Answers

Observable behavior

nil and empty slices (with 0 capacity) are not the same, but their observable behavior is the same. By this I mean:

  • You can pass them to the builtin len() and cap() functions
  • You can for range over them (will be 0 iterations)
  • You can slice them (by not violating the restrictions outlined at Spec: Slice expressions; so the result will also be an empty slice)
  • Since their length is 0, you can't change their content (appending a value creates a new slice value)

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 

Under the hood

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?

  • All slices (slice headers) have different memory addresses
  • The nil slice has 0 data pointer
  • s2 and s3 slices do have the same data pointer, sharing / pointing to the same 0-sized memory value
like image 87
icza Avatar answered Nov 04 '22 15:11

icza