Go

How do variadic functions work? What is the aliasing trap when passing s... to a function that modifies or appends to its argument?

Question 551HardGo 1.22 to 1.25

Inside the function, a variadic parameter xs ...int is a []int. A call like f(1, 2, 3) allocates a new slice. A call like f(s...) passes the caller's slice header as is: nothing is copied, so the function shares the caller's backing array.

func incr(xs ...int) {
	for i := range xs {
		xs[i]++
	}
}

func appendX(xs ...int) []int { return append(xs, 99) }

func main() {
	s := []int{1, 2, 3}
	incr(s...)
	fmt.Println(s) // [2 3 4]: caller's data mutated

	t := make([]int, 3, 10)
	_ = appendX(t[:2]...)
	fmt.Println(t) // [0 0 99]: append wrote into spare capacity, overwriting t[2]
}

Why it prints that: t[:2] has len 2 and cap 10. append sees free capacity and writes 99 at index 2 of the shared array, which is also t[2]. This is a classic bug when helpers like append(prefix, extra...) are called with a slice that is reused by the caller. Defenses: copy inside the function (slices.Clone(xs)). Or cap the slice at the call site with a full slice expression, t[:2:2], so append must reallocate. Or document that the function takes ownership. Other points: you can't mix values and a spread (f(1, s...) is a compile error), and f() passes a nil slice.

More on Go Idioms, Design Patterns & Language Design

All 16 Go Idioms, Design Patterns & Language Design questions