home

The Minimum You Need To Know About Arrays and Slices in Golang

25 Nov 2013

Most people who start to learn Go understand the concept of a slice. It's a lightweight wrapper around an array which can, but doesn't necessarily have to, represent a subset (or slice) of that underlying array. Despite this understanding, developers continue to get tripped up when putting that knowledge to practice. Why?

The problem arises when you want to pass an array to a function. Go's documentation makes it clear that arrays are passed by copy. Useful perhaps, if you want the array to be immutable. In most cases though, it's simply an inefficiency. Initially, you might be tempted to do something like:

names := []string{"leto", "paul", "teg"}
process(&names)

func process(names *[]string) {
  ...
}

That might set off your spidey sense. So you head off to the golang-nuts user group, read relevant posts, or maybe ask a question, and the answer you inevitably get back is: don't pass arrays, pass slices.

That kinda fits with what you know, since slices are thin wrappers around arrays. But still, this only feels marginally better:

names := []string{"leto", "paul", "teg"}
process(names[0:])

func process(names []string) {
  ...
}

You think to yourself, Who are these people who only ever deal with slices of arrays? 95% of the time, I need all the data in the array, not just part of it!

And here, we get to the two fundamental misconceptions people have when they think about arrays and slices.

First, think of a slices ability to represent part of the underlying array as a secondary benefit. First and foremost, a slice is a reference to an array. A slice which represents the entire array is extremely common. The second point makes this much more absolute.

Second, you are almost certainly already dealing with a slice. names above, is already a slice (which covers the entire underlying array). The only time you're dealing with an array is when you create it with a size:

  names := [3]string{"leto", "paul", "teg"}
  //or
  names := [3]string{}

Everything else, is a slice:

  names := []string{"leto", "paul", "teg"}
  //or
  names := make([]string, 3)
  //or
  var names []string

So, the solution to the above code ends up being the most intuitive:

names := []string{"leto", "paul", "teg"}
process(names)

func process(names []string) {
  ...
}

Let's take this a step further and use this knowledge to clarify another common issue. Should you create an array of pointers. For example, which is better?

  sayans := []*Sayan{
    &Sayan{Name: "Goku", Power: 9001,}
  }
  //or
  sayans := []Sayan{
    Sayan{Name: "Goku", Power: 9001,}
  }

In both cases, sayans is a slice. Therefore, when it comes to passing this to a function (or returning it), they are the same. Whether or not the extra indirection provided by the first example is useful depends on the situation, but if you aren't sure, it probably isn't, and you should be using the 2nd version.

The two important points are:

  1. Slices are references to arrays which happen to have to ability to represent a subset, and
  2. Almost everything is already a slice

I will repeat an old complaint I have about arrays and slices. The power of slices comes from their absolute transparency. As a side effect you don't know what you're actually dealing with. This could be a problem if you're using a library by someone who hasn't read this blog post ;) Still, what I've come to accept is to treat "arrays" that you get from functions as slices, because, in an overwhelming number of cases, that's exactly what they are.

Hopefully that helps.

blog comments powered by Disqus