Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go Tour #11 - Why this slice changes its capacity?

Tags:

go

In this slide of A Tour of Go, there is this snippet:

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // Slice the slice to give it zero length.
    s = s[:0]
    printSlice(s)

    // Extend its length.
    s = s[:4]
    printSlice(s)

    // Drop its first two values.
    s = s[2:]
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

and the result:

len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

My confusion is about the cap=4 in the last line - I thought it should remain 6.

What is the cause of the capacity changing from 6 to 4 in the last line?

Also, why only the last line changes its capacity but the others don't ?

like image 692
akai Avatar asked Feb 22 '18 08:02

akai


People also ask

What is a tour of go?

A Tour of Go is an introduction to the Go programming language. Visit https://tour.golang.org to start the tour.

What is go used for?

Go (also called Golang or Go language) is an open source programming language used for general purpose. Go was developed by Google engineers to create dependable and efficient software. Most similarly modeled after C, Go is statically typed and explicit.

Why is Golang good?

What is Golang Useful for? Golang is useful for carrying out programming for scalable servers and large software systems. The Golang programming language was built to fill in the gaps of C++ and Java that Google came across while working with its servers and distributed systems.


2 Answers

Remember that slice holds data in an array. By dropping first two elements we moved the beginning of the slice to the right and now there are fewer slots between the start of the slice inside the array and the end of the array.

Droping elements at the end of the slice has no effect on capacity as the distance between start of the slice inside the array and the end of the backing array does not change.

Neither of the operations modify the backing array, they just modify the slice data.

See https://blog.golang.org/go-slices-usage-and-internals the observed behaviour is explained in section Slice internals

By printing the slice header you can see the changes happening

func printSlice(s []int) {
    sh := (*reflect.SliceHeader)(unsafe.Pointer(&s))
    fmt.Printf("header=%+v len=%d cap=%d %v\n", sh, len(s), cap(s), s)
}

In the last call, the data pointer is moved ahead.

header=&{Data:272990208 Len:6 Cap:6} len=6 cap=6 [2 3 5 7 11 13]
header=&{Data:272990208 Len:0 Cap:6} len=0 cap=6 []
header=&{Data:272990208 Len:4 Cap:6} len=4 cap=6 [2 3 5 7]
header=&{Data:272990216 Len:2 Cap:4} len=2 cap=4 [5 7]
like image 79
Grzegorz Żur Avatar answered Oct 13 '22 00:10

Grzegorz Żur


Basically a slice has 3 elements :

  1. pointer to data
  2. length
  3. capacity.

The pointer to data is a reference to an underlying array (fixed size, contiguous allocation).

One can take any subset of this slice to form another slice (all that changes is the pointer to data, start of the slice.).

When one selects a subset 0 to m (m > 0) elements from the left of a slice sized N (m < N), then one has items 0 to m in the new slice and at the same time the entire underlying array capacity available (as the pointer to data still points to location 0). So one can expand it till N without any change in capacity.

However if one slices m (m > 0) to k (k < N), then the pointer to start of data is now pointing to location of m and the new length is from m to k - but now we only have capacity m to N in the new slice. We have lost access of elements 0 to m in the new slice forever. Once we understand that it's just a pointer manipulation similar to other languages/architectures, it's fairly simple. Hope this helps.

like image 25
Ravi R Avatar answered Oct 13 '22 00:10

Ravi R