Slice gotchas
PHP arrays never surprise you. Assign one, pass it to a function, slice it with array_slice: you always get your own copy, and nothing you do to it reaches the original. (PHP is clever about this behind the scenes: it only really copies when someone writes.) Go arrays work the same way, as 2.1 showed.
Slices don't. A slice is a small header pointing at a backing array (2.2), and copying the header doesn't copy the array. So two slices can quietly share the same elements:
%%{init: {"flowchart": {"wrappingWidth": 400}}}%%
flowchart TB
accTitle: Two slices sharing one backing array
accDescr: stops is a slice of length 4 over a backing array holding Bank, Moorgate, Old Street and Angel. first, made with stops[:2], is a second slice of length 2 and capacity 4 that points at the same array. Writing through either slice changes the elements both of them see.
stops["stops<br/>len 4, cap 4"] --> arr["Bank | Moorgate | Old Street | Angel"]
first["first := stops[:2]<br/>len 2, cap 4"] --> arr
That sharing is why slices are cheap to pass around. It's also the source of every bug in this lesson.
Assigning and slicing share
same := stops // a second header, same array same[0] = "Oval" // stops[0] is "Oval" now too first := stops[:2] first[1] = "Euston" // and stops[1] is "Euston"
Neither same nor first is a copy. Each is another view of the same four elements.
Functions can change your elements
Passing a slice to a function passes the header. The function can't change your length or capacity, because those were copied, but it can change the elements, because those are shared:
func shout(stops []string) { for i := range stops { stops[i] = strings.ToUpper(stops[i]) } }
Call shout(route) and route is in capitals afterwards. A PHP function doing the same to an array parameter would only change its own copy. Watch out for standard library functions that work in place: slices.Sort and slices.Reverse rearrange the slice you give them, and don't return a new one.
The flip side: an append inside the function doesn't show up in the caller's slice. The caller's length never changed, so the new element sits beyond its view (or in a different array altogether). That's one more reason Go functions return the slice they appended to.
append can overwrite a neighbour
This is the one that really hurts. A sub-slice has the original's spare capacity, so appending to it writes into the original:
| 09:00 | first := stops[:2] |
|---|---|
| 09:01 | first = append(first, "Euston") |
| 09:02 | stops[2] is now "Euston" |
| 09:03 | "But I never touched stops!" |
The same thing happens with any slice that has spare capacity, and slices grown with append usually do. Append twice to the same base slice and both results can share the same last slot, so the second append overwrites the first's value. The exercise is exactly that bug.
Asking for a real copy
When the result must be independent, make a copy explicitly:
slices.Clone(s)returns a new slice with its own backing array. It's the everyday answer.copy(dst, src)copies elements into a slice you've already made, and returns how many it copied: the shorter of the two lengths.s[low:high:max], the full slice expression, caps the new slice's capacity atmax. Withstops[:2:2]there's no spare room, so the nextappendhas to allocate a fresh array instead of writing intostops.
Mind the gap
"Does this change it, or give me a new one?" In PHP the answer is nearly always "a new one", because arrays are copied. In Go you have to know. A function that takes a slice can change its elements, and append can write into memory that another slice can see. When a function builds a new slice from one it was given, clone first. When it deliberately changes the slice in place, say so in its doc comment.
Laravel corner
You've met this question before, with Collections. They're objects, so $b = $a shares one Collection, and their methods split into ones that return a new Collection (map, filter, sortBy) and ones that change it in place (transform, push, pop). Go's slices have the same split, minus the safety net of a class that documents which is which.
Recap
- Assigning, slicing and passing a slice all share the backing array. Only arrays (and PHP arrays) copy.
- A function can change the elements of a slice you pass it.
slices.Sortandslices.Reversework in place. appendon a slice with spare capacity writes into shared memory, which can overwrite another slice's elements.slices.Clonemakes an independent copy,copyfills an existing slice, ands[a:b:b]limits capacity.