Arrays, Slices and Basic OOP in Go

Share this article

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

Key Takeaways

  • Arrays in Go are always fixed in size and the size of an array is part of its definition. This means that arrays of different sizes are considered different in Go. However, Go also has the concept of a Slice, which provides the ability to work with arrays in a dynamic, mutable sense.
  • Slices in Go are linked to the underlying slices or arrays of which they’re composed. This means that changing a slice also changes the underlying array or slice. Slices can be resized using the built-in append function, which makes a slice from two or more existing slices.
  • Maps in Go are similar to associative arrays in PHP, Dictionaries in Python, or hashes in Ruby. They are a named list of key/value pairs. However, the key and value types in Go Maps are static, not dynamic, meaning once you’ve specified their types, you can only use those types in your Map.
  • Go doesn’t provide traditional support for Object Oriented Programming (OOP). Instead, Go encourages the use of Structs as the basis of a class and adding methods onto the Structs afterwards. This is done by specifying the receiver of the method when creating it.

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?

Frequently Asked Questions (FAQs) about Arrays, Slices and Basic OOP in Go

How do I initialize a slice in Go?

In Go, you can initialize a slice in several ways. The most common way is to use the built-in make function. For example, s := make([]int, 5) creates a slice of integers with a length and capacity of 5. Another way is to use a slice literal, which is like an array literal without the length. For example, s := []int{1, 2, 3} creates a slice of integers with the values 1, 2, and 3.

How can I implement resizable arrays in Go?

In Go, slices are more flexible and powerful than arrays, and they can be resized using the built-in append function. When the underlying array of a slice is full, append creates a new array with double the size, copies the elements over, and returns a slice that references the new array. For example, s := []int{1, 2, 3}; s = append(s, 4) adds the value 4 to the slice, resizing it if necessary.

Can I initialize a slice with specific values in Go?

Yes, you can initialize a slice with specific values using a slice literal. For example, s := []int{1, 2, 3} creates a slice of integers with the values 1, 2, and 3. You can also use the ... operator to create a slice from an array or another slice. For example, a := [3]int{1, 2, 3}; s := a[:] creates a slice that references the array a.

What is the difference between arrays and slices in Go?

In Go, arrays are fixed-size sequences of elements of the same type, while slices are flexible views into the elements of an array. Slices can be resized using the built-in append function, but arrays cannot. Slices are more commonly used in Go because they are more flexible and powerful than arrays.

How does object-oriented programming (OOP) work in Go?

Go is not a traditional object-oriented programming language, but it does have types and methods, which can be used to achieve similar effects. In Go, you can define methods on any type, not just classes, and there is no concept of inheritance. Instead, Go encourages the use of interfaces and composition to achieve polymorphism and code reuse.

How can I create a method in Go?

In Go, you can create a method by defining a function with a receiver. The receiver appears in its own argument list between the func keyword and the method name. For example, func (t MyType) MyMethod() {} defines a method named MyMethod on the type MyType.

How can I use interfaces in Go?

In Go, an interface is a collection of method signatures. A type implements an interface by providing methods with the same signatures. You can use interfaces to write functions that can operate on different types. For example, func DoSomething(i MyInterface) {} can do something with any type that implements the MyInterface interface.

How does composition work in Go?

In Go, composition is achieved by embedding one type inside another. The methods of the embedded type become methods of the outer type, but they are invoked on the value of the embedded type. For example, if type A struct { B } and B has a method M, then A also has a method M that is invoked on a B.

How can I create a slice of slices in Go?

In Go, you can create a slice of slices just like any other slice. For example, s := make([][]int, 5) creates a slice of slices of integers with a length and capacity of 5. Each element of the outer slice is itself a slice, which can be resized independently using the append function.

How can I convert a slice to an array in Go?

In Go, you cannot directly convert a slice to an array because they have different types and properties. However, you can copy the elements of a slice into an array using the built-in copy function. For example, s := []int{1, 2, 3}; var a [3]int; copy(a[:], s) copies the elements of the slice s into the array a.

Matthew SetterMatthew Setter
View Author

Matthew Setter is a software developer, specialising in reliable, tested, and secure PHP code. He’s also the author of Mezzio Essentials (https://mezzioessentials.com) a comprehensive introduction to developing applications with PHP's Mezzio Framework.

arraydynamic languagegooopstatic typing
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week