Today, I was sliced by Go’s slices. Actually, by Go’s variadics. Question: what does this snippet print?

func main() {
	nums := []int{1, 2, 3}
	PrintSquares(nums...) // variadic expansion
	fmt.Printf("2 %v\n", nums)
}

func PrintSquares(nums ...int) {
	for i, n := range nums {
		nums[i] = n * n
	}
	fmt.Printf("1 %v\n", nums)
}

Answer (Playground):

1 [1 4 9]
2 [1 4 9]

🫠

Meaning, in Go, when you use a slice for variadic expansion (s...), and you use a variadic parameter to capture said slice (paramSlice ...int), they are the same1 slice, and mutating one will mutate the other.

In Python, you actually can’t do that because *args is always a tuple:

def check(*args):
    args[1] = "hi zev" # TypeError: 'tuple' object does not support item assignment

l = [1, 2, 3]
check(*l)
print(l)

So the assignment fails, but even with **kwargs:

def check(**kwargs):
    kwargs["1"] = "hi zev"

d = {"1": None}
check(**d)
assert d["1"] is None, "Sanity prevails! 😌"

We get a new dictionary in the callee, and sanity prevails.

And, TBH, I’ve been putting up with Go’s… peculiarities for a while now, but every now and then there’s something like this, where I feel like Go wants me to die an early death from high blood pressure.

Which it probably doesn’t. But I can’t shake that feeling.

$ /usr/bin/time go build
       49.82 real        59.75 user        27.26 sys
$ 

Why don’t you print anything, Go? WHY?

The Go Gopher, sans Mouth

why? 😔

If you liked this, you might also like The story of the craziest __init__ I’ve ever seen.


  1. They are almost the same: they share the same underlying array. You do get a copy of the little (ptr, len, capacity) struct which is what a slice is.
    If you reassign the variable, e.g nums = append(nums, 16), that’s a different story can of worms entirely. ↩︎