This article refers to Golang slice append implementation details.
Welcome to a deep dive into Go slices! In this article, we unravel the inner workings of Go’s slice data structure, answering fundamental questions that every Go developer should know. You’ll gain insights into how slices are structured, how their capacity dynamically grows when elements are appended, and crucially, how to avoid unintentional data overlaps or modifications when manipulating multiple slices using append().
By the end, you’ll have a clear understanding of how slices manage memory, expand capacity, and maintain data integrity in your Go applications. Let’s dive in!
- Slice data structure: How a slice looks like?
- Slice capacity growth: How a slice’s capacity increases as elements are appended?
- Avoiding data contamination: How to prevent unintended data overlap or modification when using
append()with multiple slices?
Slice Data Structure
type slice struct {
array unsafe.Pointer // a pointer to array
len int // length
cap int // capacity
}
How Does Slice Capacity Grow?
- Capacity growth:
cap * 2ifcap ≤ 256.cap * 1.25ifcap ≥ 256.
func main() {
slice1 := []int{0}
fmt.Printf("Slice1: %v, address: %p\n", slice1, &slice1)
slice2 := append(slice1, 1)
fmt.Printf("Slice2: %v, address: %p\n", slice2, &slice2[0])
slice2 = append(slice2, 2)
fmt.Printf("Slice2: %v, address: %p\n", slice2, &slice2[0])
slice2 = append(slice2, 3)
fmt.Printf("Slice2: %v, address: %p\n", slice2, &slice2[0])
slice2 = append(slice2, 4)
fmt.Printf("Slice2: %v, address: %p\n", slice2, &slice2[0])
slice3 := append(slice2, 7)
fmt.Printf("Slice3: %v, address: %p\n", slice3, &slice3[0])
}
Output:
Slice1: [0], address: 0x140000b4000 cap = 1
Slice2: [0 1], address: 0x140000a4030 cap = 2
Slice2: [0 1 2], address: 0x140000b8020 cap = 4
Slice2: [0 1 2 3], address: 0x140000b8020 cap = 4
Slice2: [0 1 2 3 4], address: 0x140000b2080 cap = 8
Slice3: [0 1 2 3 4 7], address: 0x140000b2080 cap = 8
How to Avoid Data Contamination While Using append() on Different Slices?
s1 := make([]int, 0)
s1 = append(s1, 1) // s1: [1], len=1
s1 = append(s1, 2) // s1: [1,2], len=2
s2 := append(s1, 10) // s2: [1,2,10], len = 2(from s1) + 1 = 3
s1 = append(s1, 20) // s1: [1,2,20], len = 2(from s1) + 1 = 3
s3 := append(s2, 30) // s3: [1,2,20,30], len = 3(from s2) + 1 = 4
To create a new address space to store the new slice, rather than using the same address space:
- First, create a new slice using:
newSlice := make([]int, len, cap)
copy(newSlice, oldSlice)
- Then append to the new slice.
newSlice = append(newSlice, newData)
Do not do:
newSlice := append(oldSlice, newData)