Fundamentals 24 min read

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.

Xueersi Online School Tech Team
Xueersi Online School Tech Team
Xueersi Online School Tech Team
Understanding Go Slice Internals: Structure, Initialization, Append, Slicing, Copy, and Parameter Passing

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 slice1

Value 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 array

References

1. "深入解析 Go 中 Slice 底层实现" 2. "理解 Go 中的 Slice" 3. "深度解密 Go 语言之 Slice" 4. "The Go Programming Language Specification"

golangGoRuntimememoryappendsliceCopy
Xueersi Online School Tech Team
Written by

Xueersi Online School Tech Team

The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.