Slices gotcha

s := []int{1,2,3}
t := s[:2]
t = append(t, 100)
Println(t) // {1,2,100}
Println(s) // ?

If you’ve said {1,2,100}, good on you! You can just quickly skim through this section.

In go, an array is a contiguous piece of memory that has a fixed length. However, dynamically sized arrays are often more useful for a programmer. Go’s solution to this is slices. A slice is another datastructure that contains a pointer to the underlying array, and two values for the length and capacity.

Nothing surprising there, yet. The idiom for adding things to the dynamic array is through append. Suppose our original slice was

s := []int{1,2,3}

To add another integer, we use append,

s = append(s, 4)

We can create new slices from old ones

t := s[:2]

It is no surprise that if we modify t[0], s[0] will change as well since t holds the pointer to the underlying array, which is shared by s as well. But what happens to s when we do t = t.append(100)?

s[2] becomes 100 of course!

When append is called, it checks if there is capacity in the underlying array. If there is, then it assigns it to that block. If there isn’t, it creates a new array, copies all the old value, and assigns the value to the correct ‘block’. In our example, there is enough capacity since our underlying array has at least capacity 3.

What about this code?

s := []int{1,2}
t := s[:2]
t = append(t, 100)
Println(t) // {1,2,100}
Println(s) // ?

Here when we called append, the underlying array only has two value, so it creates a new array with length 3 and copies the old value over so t no longer holds a reference to the underlying array of s.

For range

package main

import (
    "fmt"
    "time"
)

func main() {
    s := []int{1, 2, 3, 4}
    for _, value := range s {
        go func() {
            fmt.Println(value)
        }()
    }
    time.Sleep(2)
}

If you guess that the output is 4,4,4,4, you’re golden and you can skim through this section.

The reason for this is that value is a reference type, and on each iteration, its value points to the array address with offset given by the iteration number. When we run a goroutine that uses value, it gets the last iteration value.