Understanding Go Slice Internals: Structure, Initialization, Append, Slicing, Copy, and Parameter Passing
This article explains the internal representation of Go slices, how they are created and initialized, the mechanics of append and capacity growth, slicing behavior, deep‑copy using the copy function, and whether slices are passed by value or reference, all illustrated with source code and assembly excerpts.
Slice Structure
In Go, a slice is a three‑field struct defined in runtime/slice.go :
type slice struct {
array unsafe.Pointer // pointer to the underlying array
len int // current length
cap int // capacity
}The struct occupies 24 bytes on a 64‑bit platform.
Slice Initialization
Creating a slice with make([]int, 0) allocates the slice header and a zero‑length underlying array. The compiled assembly shows calls to runtime.makeslice which calculates the required memory and invokes mallocgc .
Append Operation
Appending to a slice calls runtime.growslice when the capacity is insufficient. For capacities ≤ 1024 the new capacity is doubled; for larger capacities it grows by roughly 1.25×, followed by memory alignment via runtime.roundupsize . The article provides assembly snippets demonstrating these calls.
Capacity Growth Example
A program that repeatedly appends integers prints the capacity changes, showing the doubling pattern up to 1024 and the 1.25× growth thereafter (e.g., 1024 → 1280 → 1696 → …).
Slice Slicing
Slicing creates a new slice header that shares the same underlying array. The length becomes the slice range, while the capacity extends to the end of the original array. Modifying elements through one slice affects all slices that share the array, unless a later append triggers a reallocation.
slice := []int{0,1,2,3,4,5,6,7,8,9}
s1 := slice[2:5] // len=3, cap=8
s2 := s1[2:7] // len=5, cap=6 (shares underlying array)Deep Copy with copy
The built‑in copy function performs a shallow copy of elements from a source slice to a destination slice. To achieve a deep copy you must allocate a destination slice with sufficient capacity; the copy then moves data via runtime.memmove , leaving the original slice unchanged.
slice1 := []int{0,1,2,3,4,5,6,7,8,9}
slice3 := make([]int,5)
copy(slice3, slice1) // copies first 5 elements
slice3[0] = 100 // does not affect slice1Value vs. Reference Passing
When a slice is passed to a function, the slice header (pointer, length, capacity) is passed by value. Changing the header inside the function (e.g., re‑assigning in = append(in, 5) ) does not affect the caller, but modifying the underlying array (e.g., in[0] = 100 ) does affect all slices sharing that array.
func fn(in []int) { in = append(in, 5) } // no effect on caller
func fn2(in []int) { in[0] = 100 } // modifies underlying arrayReferences
1. "深入解析 Go 中 Slice 底层实现" 2. "理解 Go 中的 Slice" 3. "深度解密 Go 语言之 Slice" 4. "The Go Programming Language Specification"
Xueersi Online School Tech Team
The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.