Arrays, Slices and Basic OOP in Go

Matthew Setter
Share

Recently I wrote an introduction to using Go, looking at what it is, how to install it, and how to get your environment ready. The article finished up by stepping through a small application covering the Go basics of variables, output, functions and structs.

In today’s article, we’ll build on that foundation, by looking at a few new concepts; specifically:
– Arrays & Slices
– Maps
– Methods

Arrays & Slices

There’s many ways in which you can initialize an array in Go. Let’s have a look through 5 examples:

[golang]
var numberList [5]int
var stringList [10]string
var float64List [15]float64
[/golang]

Here, we’ve created three empty arrays, of different types (int, string and float64) and different sizes (5, 10 and 15).

[golang]
x := [5]float64{ 98, 93, 77, 82, 83 }
[/golang]

Here, we’ve created an array, 5 elements in size of type float64, initializing it at the same time.

[golang]
y := […]int{ 98, 93, 77, 82, 83 }
[/golang]

Here, we’ve created an array of type int, but not specified the size. Instead, we’ve passed in ..., and a range of values. When initializing the array this way, it will determine the size from the number of elements provided.

If you are familiar with dynamic languages, such as Python, PHP and Ruby, you’ll be used to creating arrays of a fixed size, with elements of a mixed or same type, but also be able to dynamically add and remove elements as you go along. Here’s a PHP example:

$userList = array("Matthew", "Bruno", "Peter", "Chris");
unset($userList[0]);
$userList = array_merge(
    $userList, array("Jaco", "James", "Michael")
);
var_dump($userList); //"Bruno", "Peter", "Chris", "Jaco", "James", "Michael"

We initialized $userList to an array, containing four names, then removed the first element and added three new ones on the end using array_merge. Unfortunately We can’t do that in Go, as arrays are always fixed in size. Here’s an example of trying to do so:

[golang]
var buffer [5]string
buffer[0] = "Robert"
buffer[1] = "Michael"
buffer[2] = "James"
buffer[3] = "Aaron"
buffer[4] = "Simon"
buffer[5] = "Alex"
[/golang]

We first initialized a new variable, buffer, to a 5 element string. We then attempted to deliberately add 6 elements to it, more than its capacity. If we tried to run this we’d receive the following error:

# command-line-arguments
invalid array index 5 (out of bounds for 5-element array)

See unlike dynamic languages, in Go the size of an array is a part of its definition; so var buffer [5]string and var buffer [6]string aren’t the same thing, as you’ve probably come to expect.

This really isn’t an issue, because Go has the concept of a Slice. As Rob Pike (one of the Go’s creators) said:

Arrays are an important building block in Go, but like the foundation of a building they are often hidden below more visible components.

One of the more visible components are slices. Slices are much like what we’d be used to using in PHP, Ruby and Python. Slices provide the ability to work with arrays but in a dynamic, mutable sense.

However, one thing which has caught me up a bit as I’ve been learning Go, is that that’s a bit too simplistic of a definition. Here’s a more specific definition, from the Effective Go book:

Slices hold references to an underlying array, and if you assign one slice to another, both refer to the same array.

This might seem a bit strange at first. It sure was to me, as I’ve been working purely in dynamic languages for some time. But it makes sense after a short while.

This has been a bit of a rush through both Arrays and Slices, so let’s slow down a bit and look at both in more detail. Specifically, let’s look at the following 3 things:

  1. Creating a Slice From an Existing Array (or Slice)
  2. Growing a Slice
  3. Modifying a Slice

Creating a Slice From an Existing Array (or Slice)

Ok, let’s take our buffer array and create a slice from it. Have a look at the following code:

[golang]
small_buffer := buffer[1:4]
[/golang]

What this has done is to create a new slice, small_buffer, which is composed of elements 2 – 4, by specifying [1:4]. 1 specifies the element to start from, 4 specifies the element to go up to, but not include. Therefore, we have elements 1, 2 and 3.

We don’t have to specify either of these parameters. If we’d only specified :4, then we’d start at 0 and finish at element 4. If we’d only specified 2:, we’d have started at element 3 and finished at the end of buffer. So, quite flexible.

Modifying a Slice

As I mentioned earlier, slices are linked to the underlying slices or arrays of which they’re composed. So, if you changed a slice, it changes the underlying array or slice. Let’s step through an example and see the results.

[golang]
small_buffer[1] = "Rodriguez"
[/golang]

After doing this, buffer will contain "Robert", "Michael", "Rodriguez", "Aaron", "Simon", "Alex" and small_buffer will contain "Michael", "Rodriguez", "Aaron", "Simon", "Alex". So you can see that as well as modifying the slice, it also modified the underlying array.

Growing a Slice

Now what if you want to grow an array? There’s a built-in method, append, which makes this really easy. What it does is it makes a slice from two or more existing slices, and using it is very simple. Let’s look at an example:

[golang]
//Extend an existing slice
g := []string{"John", "Paul"}
h := []string{"George", "Ringo", "Pete"}
y := append(g, h…)
[/golang]

Here, we’ve created two slices: g & h, which separately contain the complete ensemble of The Beatles. But we can call append, passing in g and h, initializing a new variable y as the combination of the two. Did you notice ... following h? This expands h to a set of arguments which are individually appended to the end of g.

If this doesn’t quite make sense, have a look at this link on Variadic functions. The result is a slice containing [“John”, “Paul”, “George”, “Ringo”, “Pete”]. One thing to note, if we modify y, it won’t affect either g or h as they’re not linked.

Maps

Ok, let’s now look at Maps. Continuing my dynamic language analogy, you may be used to using associative arrays in PHP, Dictionaries in Python, or hashes in Ruby. Well, a Map in Go is really similar. In essence, it’s just a named list (or set) of key/value pairs.

The key difference to dynamic languages is that the key and value types are static, not dynamic. Once you’ve specified their types, you can only use those types in your Map. Here’s a first example so you can see what I mean.

[golang]
import (
"strings"
)

var retval = map[string]int{}
myHomeTown := "Brisbane By The Bay"

var tokens = strings.Fields(myHomeTown)
for i := 0; i < len(tokens); i++ {
retval[tokens[i]] = len(tokens[i])
}
[/golang]

What I’ve done is to initialize a new variable, retval as an empty map, which will contain strings. Then, I’ve initialized a new string variable, myHomeTown. Following this, using the Fields method in the strings package, I’ve tokenized the value of myHomeTown.

The Fields method returns an array, by splitting a string up by a space. If it helps make it clearer, tokens could have been initialized like this:

[golang]
tokens := [4]string{"Brisbane", "By", "The", "Bay"}
[/golang]

We then iterated over tokens, adding the current index’s value as the key and its length as the value. Running the code would output the following:

[golang]
map[Brisbane:8 By:2 The:3 Bay:3]
[/golang]

Now that’s one way; what about initializing the Map with values right from the start? Have a look at the example below:

[golang]
type User struct {
name, client string
age int
}

var users = map[string]User{
"settermjd": { name: "Matthew Setter", client: "Sitepoint", age: 38 },
}
fmt.Println(users)
[/golang]

Firstly, I created a new Struct called User, containing properties for name, client and age. Then I’ve created a Map, which has a string key and a User Struct as its value.

You can see that I was able to create the map and initialize the first User element in one go; quite handy. This way, we have the power of a static language, with the flexibility of a dynamic one.

Methods & OOP in Go

If you’re coming from a dynamic language background or from languages such as .Net, C++ and so on, you’ll be expecting to be able to develop using an Object Oriented (OOP) approach. However, Go doesn’t provide support for OOP, well not as you may be expecting. The approach is slightly different.

Remember the Structs we’ve been creating in this and the previous post? The first thing you need to do is think of your Structs as the basis of a class. Then, you need to add the methods onto the Structs afterwards.

This is done by specifying the receiver of the method when creating it. This might not make a lot of sense initially. So let’s work through an example.

[golang]
package main

import (
"fmt"
"strings"
)
type User struct {
FirstName, LastName, EmailAddress string
Age int
}
[/golang]

Here we’ve extended our previous Struct, User, adding in a new string property, EmailAddress.

[golang]
func (u *User) FullName() string {
return fmt.Sprintf(
"%s %s", u.firstname, u.lastname
)
}
[/golang]

Then we’ve defined a function, FullName, which takes no parameters but returns a string. What’s new is (u *User). This means that User will be the receiver of the function, effectively adding the function onto the Struct. The function itself simply returns a concatenation of the FirstName and LastName properties by calling Sprintf exported from the fmt package.

[golang]
func main() {
u := User{FirstName: "Matthew", LastName: "Setter"}
fmt.Println(u.FullName())
}
[/golang]

Now let’s use it. In the code above we’ve instantiated a new User struct, specifying only the FirstName and LastName properties. We’ve then called FullName() on it, passing the method call to fmt.Println(), which prints out Matthew Setter.

Wrapping Up

And that’s it! We’ve covered the basics of variables, output, functions and structs, then stepped up to look at arrays, slices, maps and functions.

I appreciate that, in some ways, Go isn’t as simple as a dynamically typed languages and does constrain you a bit more. But the flip side is that it gives you a lot more power, for only minimal extra effort.

Tell me what you think in the comments. Have I missed anything? Would you have approached this differently?