Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding Slice Internals and Argument Passing in Go

Tech 1
package main

import "fmt"

type sliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

func main() {
    topics := []string{"docker", "kubernetes", "istio"}
    modifyFirst(topics)
    fmt.Println(topics)
}

func modifyFirst(items []string) {
    items[0] = "terraform"
}
// Output: [terraform kubernetes istio]

A slice is passed by value, but its underlying representation causes it to behave like a reference type. This behavior stems from the slice data structure—a small header containing a pointer to a backing array, a length, and a capacity. Although the header is copied during a function call, the pointer still refers to the same underlying array.

The internal structure resembles:

type slice struct {
    ptr   unsafe.Pointer
    len   int
    cap   int
}

Example 1: Slicing and Capacity

func main() {
    nums := []int{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
    sub := nums[2:7]
    fmt.Printf("sub:%v len:%v cap:%v\n", sub, len(sub), cap(sub))
}
// Output: sub:[13 14 15 16 17] len:5 cap:8

sub shares the underlying array starting at index 2. Length is 5, but the capacity extends to the end of nums, giving 8 elements.

Example 2: Mutation Without Capacity Growth

func main() {
    base := make([]int, 1, 5)
    fmt.Println("base:", base)
    mutate(base)
    fmt.Printf("base:%v len:%v\n", base, len(base))
}

func mutate(s []int) {
    s = append(s, 66, 77)
    s[0] = 111
    fmt.Println("inside:", s)
}

Output:

base: [0]
inside: [111 66 77]
base:[111] len:1

The backing array is modified by both the first append and the index assignment. However, the original slice header still has length 1, so only base[0] reflects the change.

Example 3: Capacity Exhaustion Triggers a New Backing Array

func main() {
    original := make([]int, 1, 3)
    fmt.Println("original:", original)
    expand(original)
    fmt.Printf("original:%v len:%v\n", original, len(original))
}

func expand(s []int) {
    s = append(s, 2, 3, 4, 5) // exceeds capacity
    s[0] = 99
    fmt.Println("inside:", s)
}

Output:

original: [0]
inside: [99 2 3 4 5]
original:[0] len:1

When append exceeds the existing capcaity, a new larger array is allocated. The s header inside expand points to this new array, so modifications no longer affect original.

Passing a slice to a function never requires an explicit pointer; the header copy still references the same underlying data until a capacity-triggered allocation breaks the link.

Tags: golang

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.