Introduction To Golang: Arrays, Maps, Slices And Make

Jan 14, 2013

In Part 1 we looked at various ways to create variables, mainly by using new. What does it mean though, to new an array?

scores := new([]int)

All this gives us is pointer to an uninitialized arary. To initialize an array or a map, you need to provide additional data. For this purpose, we use make:

scores := make([]int, 50)
  scores[0] = 9001  // yay, works

We now have an initialized array value (not a pointer!) that can hold 50 integers. A wonderful thing about Go arrays (and strings) is the ability to create a slice around them. Slices point to an arbitrary start and end within the array/string. They are pointers and thus are efficient to pass around, and will change if the underlying array changes.

One real example of using slices is copying a io.Reader to an io.Writer. Here's an example that would probably NOT work:

buffer = make([]byte, 4096)
for {
  read, _ := r.Read(buffer)
  if read == 0 { break; }

Pretend our data is 10000 bytes, what will happen? Our first and second iterations will read and write 4096 bytes. Our third iteration will read 1808 bytes (10000-8192), but it will still write 4096 bytes since that's the length of our buffer. That last write will contain a mix of data from the current read as well as data from the previous read.

The solution is to use slices (remember, they are efficient):

buffer = make([]byte, 4096)
for {
  read, _ := r.Read(buffer)
  if read == 0 { break; }

Now we'll write 4096, 4096 and 1808 bytes.

Note that arrays in Go are real array: their growth is bound to the initial size.

Maps are quite similar and you initialize them, with make, specifying the type of the key and the type of the value. Maps will grow as needed:

sayans := make(map[string]int)
sayans["goku"] = 9001

Both maps and arrays can be initialized with values:

var scores = []int {1,2,3,4}
var sayans = map[string] int {
  "goku": 9001,

The last interesting point is how we retrieve a value from a map. We haven't gone over it yet (though we did just see an example of it), but Go functions can return multiple values. In our above example we assigned the second value to _ which is a throwaway variable. The proper way do this would have been:

for {
  read, err := r.Read(buffer)
  if err != nil { do something }

You'll see a lot of functions in Go that return 2 values: the actual value of interest and an error. That's how Go error handling works (dealwithit). But functions aren't limited to 2 return values nor must they necessarily return an error.

Remember that := can only be used to declare new variables. With multiple return values, the rule is that you can use := so long as one of the variables is new. For example:

  width, err := parseValue("width")
  height, err := parseValue("height")

  //not valid, 2nd line won't compile as neither size nor err are new
  size, err := parseValue("width")
  size, err := parseValue("height")

  //would need to do:
  size, err := parseValue("width")
  size, err = parseValue("height")

So you really need to keep track of your variables, which I think promotes short functions.

Back to retrieve a value from a map. Two values are returned: the requested value and whether or not the value existed. This lets you distinguish between a nil/0 value and one that was never set:

level, ok := sayans["goku"]
  if ok == false { return "invalid" }

make is used for a third type: channels. In our next post, we'll jump off the deep end and explore channels and do more advanced Go programming