Need help understanding slices

Hello all,

I am new to Go and am going through the tour. On lesson 11/27 of MoreTypes, this is the example code (I’ve modified it to clarify my question):

package main

import "fmt"

func main() {
	// Step 1: Create slice
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice("Step 1:", s)

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

	// Step 3: Extend its length.
	s = s[:4]
	printSlice("Step 3:", s)

	// Step 4: Drop its first two values.
	s = s[2:]
	printSlice("Step 4:", s)

}

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

which gives the following output:

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

I have read the go team blog post on slice internals to understand how slices work, and my understanding goes as follows:

  1. slices are abstractions on top of, or views into, arrays in memory
  2. a slice data structure contains 1) a pointer to somewhere in the underlying array, 2) an integer length referring to the length of the slice, and 3) an integer capacity referring to the size of the underlying array
  3. slices can be cropped and expanded, but arrays cannot.

So I am confused by the output of the example code. My question specifically would be “Why does the capacity of slice s change between steps 3 and 4, and not between steps 1 and 2, or between steps 2 and 3?” The output is suggesting that step 4 changes the underlying array. If this is so, why does step 4 change the underlying array and steps 2 and 3 do not?

Sorry if this question is noobish, but I am, after all, a noob. Thanks for any clarification or pointers to documentation I’ve missed.

This

s := []int{2, 3, 5, 7, 11, 13}

becomes a six element array, and a slice that points to the first element, with the length and capacity both being six.

Steps two and three change the view into the array by altering the length of the slice, but not the capacity. There’s no need to do anything to the underlying array to accomplish this.

Step four changes the first element of the slice, effectively chopping off the two integers before it. If the original slice could be visualized something like this (bear with me with ascii art and the simplification that there is a “start” index instead of an actual pointer):

 s (start=0, len=6, cap=6)
 |
 v----------------<
{2, 3, 5, 7, 11 13}

then the slice after step one is pointing to the first element and the zero following elements

 s (start=0, len=0, cap=6)
 |
 v
{2, 3, 5, 7, 11 13}

and after step three

 s (start=0, len=4, cap=6)
 |
 v---------<
{2, 3, 5, 7, 11 13}

and then finally after step four which moves the first element:

       s (start=2, len=2, cap=4)
       |
       v---<
{.. .. 5, 7, 11 13}

The cap must be reduced as the two other elements are now before the slice and inaccessible. Retaining a cap of six here would require two more elements to exist after the end of the current array.

1 Like

Aaah, this is making sense. So, in fact, step 4 does not alter the underlying array, it only removes any way to point back to the first element of the array? That is, in order to “recover” elements 2 and 3 of the array/slice, I would have to keep some slice around with a pointer to the start of the underlying array? Something like

s := []int{2, 3, 5, 7, 11, 13}
t := s[2:]    // t == {5, 7, 11, 13}, len 4, cap 4
t = s[:4]    // t == {2, 3, 5, 7}, len 4, cap 6

OK, just tried it out on the interpreter. This clarifies things for me. Thanks for the helpful ascii art.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.