Working with slice in Go.
Slice in Go can be considered as dynamic version of array.
[]T
is a slice of type T
. Length and capacity of slice can be extended at run-time. This makes slice very flexible and widely used compare to array.
A new slice can be created by using make([]T, length, capacity),
slice literals or slicing from an existing array/slice:
s0 := make([]int, 2)
s1 := make([]int, 0, 5)
s2 := make([]int, 2, 5)
s3 := []float64{}
s4 := []float64{1.0, 2.0, 3.0}
arr := [5]int{1, 2, 3, 4, 5}
s5 := arr[0:1]
s6 := arr[:]
s7 := s6[2:3]
Playground
Length is number of elements of the slice and capacity is number elements the underlying array can hold (see explain about underlying array in advanced section). And they can be calculated by using 2 built-in functions len
and cap
s := make([]int, 0, 5)
fmt.Printf("len: %d, cap: %d", len(s), cap(s))
A new element can be added into a slice using append
function:
s := make([]int, 0, 5)
s = append(s, 1)
s = append(s, 2, 3, 4, 5)
fmt.Printf("len: %d, cap: %d, values: %v", len(s), cap(s), s)
Slice element can be accessed via index and range statement can be used to range over the slice:
a := []int{1, 2, 3, 4, 5}
fmt.Println("a[1]:", a[1])
a[2] = 4
fmt.Println("a:", a)
for i, v := range a {
fmt.Printf("index: %d, value: %d\n", i, v)
}
Playground
A slice can be copied to another one by using copy
or append
:
a := []int{1, 2, 3, 4, 5}
b := make([]int, len(a))
copy(b, a)
c := append([]int(nil), a...)
d := append(a[:0:0], a...)
Playground
append
can be used to cut/delete 1 or more elements in the middle of a slice:
a := []int{1, 2, 3, 4, 5}
fmt.Println("a: ", a)
a = append(a[:1], a[2:]...)
fmt.Println("a: ", a)
a = append(a[:1], a[3:]...)
fmt.Println("a: ", a)
Playground
For more tricks of using slice, see: https://github.com/golang/go/wiki/SliceTricks
Slice can be considered as reference type. This means modifying its value inside a function will change the values of the original slice:
func main() {
a := []int{1, 2, 3, 4, 5}
changeValue(a, 0, 2)
fmt.Println("a: ", a)
}
func changeValue(a []int, i int, v int) {
a[i] = v
}
Playground
Capacity of slice will be double if its length reaches its current capacity: (more complex strategy if length of slice is greater than 1024, see slice.go)
a := make([]int, 0, 2)
a = append(a, 1, 2)
a = append(a, 3)
a = append(a, 4, 5)
a = append(a, 6)
Playground
Advanced #
A slice is actually a composite type named SliceHeader
which is composed of 3 words: Data
, Len
and Cap
. Data is a pointer that point to the first element of the underlying array that the slice can reach (not necessary the first element of the array). Len is number elements of the slice and Cap is length of the underlying array:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
A slice can be reverse back to SliceHeader
by using reflect
and unsafe
package:
a := make([]int, 0, 5)
a = append(a, 1, 2)
h := (*reflect.SliceHeader)(unsafe.Pointer(&a))
fmt.Printf("Data: %d, Len: %d, Cap: %d", h.Data, h.Len, h.Cap)
Playground
We can also reverse the pointer that point to the underlying array to an array using unsafe and type casting:
a := make([]int, 0, 5)
a = append(a, 1, 2)
h := (*reflect.SliceHeader)(unsafe.Pointer(&a))
arr := (*[5]int)(unsafe.Pointer(h.Data))
fmt.Printf("%16s: %v\n", "slice", a)
fmt.Printf("%16s: %v\n", "underlying array", *arr)
Playground
When a slice is passed into a function, a copied version of the SliceHeader
is created, but the data is still the same:
func main() {
a := make([]int, 0, 5)
a = append(a, 1, 2)
h := (*reflect.SliceHeader)(unsafe.Pointer(&a))
fmt.Printf("Main - Address: %d, Data: %d, Len: %d, Cap: %d\n", unsafe.Pointer(&a), h.Data, h.Len, h.Cap)
printSliceInfo(a)
}
func printSliceInfo(a []int) {
h := (*reflect.SliceHeader)(unsafe.Pointer(&a))
fmt.Printf("Func - Address: %d, Data: %d, Len: %d, Cap: %d\n", unsafe.Pointer(&a), h.Data, h.Len, h.Cap)
}
Playground
append
statement always return a new slice header (as of Go 1.13):
a := make([]int, 0, 5)
a1 := append(a, 1, 2)
a2 := append(a, 1, 2)
Playground
One of the reason make
always return a new SliceHeader
is because the underlying array will be re-allocated to a completely new one if length of slice reach its capacity:
a := make([]int, 0, 5)
a1 := append(a, 1, 2, 3, 4, 5, 6)
Playground
Questions #
What the below program print and why?
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s := arr[0:2]
s = append(s, 6)
fmt.Println(arr)
}
What the below program print and why?
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s := arr[0:2]
_ = append(s, 6)
fmt.Println(arr)
fmt.Println(s)
}
What the below program print and why?
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s := arr[0:2]
s1 := arr[1:3]
s = append(s, 6)
fmt.Println(s)
fmt.Println(s1)
}