Slices

Station 2.2 14 min Interchange: $xs[] = ... & count() & array_slice() → []T

When PHP code needs a list, it writes $stops[] = 'Bank'; and keeps going. The Go version of that list is a slice, and it's the collection you'll use more than all the others put together.

A slice type looks like an array type with the number left out: []string is "a slice of strings", of any length.

Making one

stops := []string{"Bank", "Angel"} // a literal
var later []string                 // nil
counts := make([]int, 8)           // eight zeros

The zero value of a slice is nil, and a nil slice is perfectly usable. Its len is 0, range over it does nothing, and you can append to it. So var stops []string is the usual way to start an empty list you're about to fill.

make([]int, 8) builds a slice that already has eight elements, all at their zero value. It's PHP's array_fill(0, 8, 0).

append

There's no $stops[] =. You add to a slice with append, and assign the result back:

stops = append(stops, "Old Street")

// several at once
stops = append(stops, "Angel", "Euston")

// all of another slice, spread as in 1.2
all := append(north, south...)

That assignment isn't optional. Write append(stops, "Old Street") on its own and the build fails with append(stops, "Old Street") (value of type []string) is not used. To see why, you need to know what a slice really is.

What a slice really is

A slice is a small value with three parts: a pointer to an array (the backing array), a length and a capacity. The length is how many elements you can use, len(s). The capacity is how many the backing array has room for, cap(s).

When you append, Go checks for room:

%%{init: {"flowchart": {"nodeSpacing": 18}}}%%
flowchart TB
  accTitle: What append does
  accDescr: append first checks whether the backing array has spare capacity. If it does, the new value goes into the spare space, and append returns a slice over the same array with the length one bigger. If it doesn't, Go allocates a new, bigger array, usually twice the size, copies the existing values across, adds the new one, and returns a slice over the new array. Either way the result is a new slice value, so you have to assign it.
  start["append(stops, x)"] --> q["Room left?<br/>len(stops) < cap(stops)"]
  q -->|yes| same["Use the spare space<br/>same array"]
  q -->|no| grow["New, bigger array<br/>copy across, add x"]
  same --> ret["Return the new slice<br/>stops = append(…)"]
  grow --> ret

Either way, the slice you get back is different from the one you passed in: a longer length, and sometimes a whole new array. That's why append returns it, and why you assign it back. Growth roughly doubles the capacity each time, so appending in a loop stays fast.

Slicing

You can take part of a slice with the same [start:end] you used on strings in 1.5. stops[1:3] is indexes 1 and 2, and either side can be left out: stops[:2], stops[2:].

Unlike array_slice, this doesn't copy anything. The new slice points into the same backing array. That's fast, and it's also the source of Go's most surprising slice bugs, so it gets its own lesson next (2.3).

A few PHP habits that don't carry over:

  • No negative indexes. The last element is stops[len(stops)-1].
  • Removing the last element is a slice expression, stops = stops[:len(stops)-1], rather than array_pop.
  • Indexing never grows a slice. stops[10] = "Bank" on a shorter slice panics with index out of range. Only append adds elements.

Mind the gap

make with a length, then append, puts your values after the zeros. make([]int, 3) already holds [0 0 0], so appending 7 gives [0 0 0 7]. Either set elements by index (xs[i] = v) or ask make for capacity only: make([]int, 0, 3) has length 0 and room for 3, so append starts at the front. This one turns up in real code all the time, and it's waiting in the exercise.

Tip

var xs []string (nil) and xs := []string{} (empty but not nil) behave the same almost everywhere: same len, same range, same append. The difference shows up when you turn them into JSON, where nil becomes null and empty becomes []. Lesson 7.3 comes back to it.

Recap

  • []T is a slice: a list of any length. Its zero value, nil, is ready to use.
  • Add with xs = append(xs, v). Always assign the result.
  • A slice is a pointer to a backing array, plus a length and a capacity. append grows the array when it runs out of room.
  • make([]T, n) gives you n zero values. make([]T, 0, n) gives you room for n.
  • xs[a:b] shares the original's backing array. More on that in 2.3.

1Building a list

PHPbuild.php
<?php
$stops = [];
$stops[] = 'Bank';
$stops[] = 'Moorgate';

echo count($stops); // 2
GObuild.go
var stops []string // nil, but ready to use
stops = append(stops, "Bank")
stops = append(stops, "Moorgate")

fmt.Println(len(stops)) // 2

Mind the gap

There's no $xs[] =. append returns the new slice, and you assign it back. Forget the assignment and it doesn't compile.

2Taking part of a list

PHPslicing.php
<?php
$stops = ['Bank', 'Moorgate', 'Old Street', 'Angel'];

array_slice($stops, 1, 2); // offset 1, length 2
// ['Moorgate', 'Old Street']
GOslicing.go
stops := []string{"Bank", "Moorgate", "Old Street", "Angel"}

part := stops[1:3] // index 1 up to, not including, 3
// [Moorgate Old Street]

Mind the gap

Start and end, not offset and length, the same as slicing a string in 1.5. And no copy is made: the result shares the original's storage (lesson 2.3).

3Merging two lists

PHPmerge.php
<?php
$north = ['Angel', 'Euston'];
$south = ['Bank', 'Borough'];

$all = [...$north, ...$south];
GOmerge.go
north := []string{"Angel", "Euston"}
south := []string{"Bank", "Borough"}

all := append(north, south...)

Mind the gap

The spread from 1.2, with the dots after the slice. append takes any number of values, so south... passes them all.

4A list of a set length

PHPmake.php
<?php
$counts = array_fill(0, 8, 0); // eight zeros
$counts[3] = 81;
GOmake.go
counts := make([]int, 8) // eight zeros
counts[3] = 81

Mind the gap

make builds a slice that already has a length, every element at its zero value. Indexing past that length panics rather than growing the slice.