Go by Example: Slices

A Slices is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).

ref: https://go.dev/blog/slices-intro

package main
import "fmt"
func main() {
  1. declare array
    var a [3]int
    b := [3]int{1, 2, 3}
    c := [...]int{1, 2, 3, 4}
    fmt.Println("a", a)
    fmt.Println("b", b)
    fmt.Println("c", c)
  1. modify array
    a[2] = 31
    fmt.Println("modify array a", a)
  1. slice is just pointer to its underlying array underlying array: [3]int{1, 2, 3}
    s1 := []int{1, 2, 3}

underlying array: [6]int{0, 0, 0, 0, 0, 0}

    s2 := make([]int, 3, 6)
    fmt.Println("s1", s1, "cap", cap(s1), "len", len(s1))
    fmt.Println("s2", s2, "cap", cap(s2), "len", len(s2))
  1. copy slice with slicing operator
    b1 := s1[1:3]
    fmt.Println("copy slice with slicing operator")
    fmt.Println("b1", b1, "cap", cap(b1), "len", len(b1))

all the subsequent slices use the same underlying array under the hood!

    s1[2] = 42
    fmt.Println("b1", b1, "cap", cap(b1), "len", len(b1))
  1. copy slice with copy (new memory allocation)
    s3 := []int{1, 2, 3}
    b3 := make([]int, len(s3))

same as “copy” func see doc at: go doc builtin.copy

    copy(b3, s3)
    fmt.Printf("copy %v to new array %v\n", s3, b3)

or inline copy with append

    fmt.Printf("inline copy %v with append: %v\n",
        s3, append([]int{}, s3...))

will not be affected by modifying s1

    fmt.Println("b3", b3, "cap", cap(b3), "len", len(b3))
    s3[2] = 41
    fmt.Println("b3", b3, "cap", cap(b3), "len", len(b3))
  1. convert static array to slice
    key := [16]byte{}
    keyS := key[:]
    fmt.Println("keyS", keyS, "cap",
        cap(keyS), "len", len(keyS))
  1. increase the length of slice
    s4 := make([]int, 3, 10)
    fmt.Println("s4", s4, "cap", cap(s4), "len", len(s4))

increase the length of slice with slicing operator works up until the cap(s4)

    s4 = s4[:5] // len=3, cap=5
    fmt.Println("s4", s4, "cap", cap(s4), "len", len(s4))
    s5 := []int{1, 2, 3}
    fmt.Println("s5", s5, "cap", cap(s5), "len", len(s5))

trigger the capacity increase since reached the threshold

    s5 = append_(s5, 31) // cap 3 -> 8, len 3 -> 4
    fmt.Println("s5", s5, "cap", cap(s5), "len", len(s5))
}
func copySlice[K any](dst, src []K) int {
    var n int
    for i, _ := range src {
        dst[i] = src[i]
        n++
    }
    return n
}

simulate the append of go internal

func append_[K any](s []K, e K) []K {
    expLen := len(s) + 1
    if cap(s) < expLen {

grow the slice new allocation create new slice from scratch

        newS := make([]K, expLen, (cap(s)+1)*2)

copy the data from old to new

        copySlice(newS, s)

assign new to old

        s = newS
    }

grow len

    s = s[:expLen]

assign the element

    s[expLen-1] = e
    return s
}

Note that while slices are different types than arrays, they are rendered similarly by fmt.Println.

a [0 0 0]
b [1 2 3]
c [1 2 3 4]
modify array a [0 0 31]
s1 [1 2 3] cap 3 len 3
s2 [0 0 0] cap 6 len 3
copy slice with slicing operator
b1 [2 3] cap 2 len 2
b1 [2 42] cap 2 len 2
copy [1 2 3] to new array [1 2 3]
inline copy [1 2 3] with append: [1 2 3]
b3 [1 2 3] cap 3 len 3
b3 [1 2 3] cap 3 len 3
keyS [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] cap 16 len 16
s4 [0 0 0] cap 10 len 3
s4 [0 0 0 0 0] cap 10 len 5
s5 [1 2 3] cap 3 len 3
s5 [1 2 3 31] cap 8 len 4

Check out this great blog post by the Go team for more details on the design and implementation of slices in Go.

Next example: Performance: Slice-Append.