DEV Community

Cover image for Learning GO: The container types
Andrew Bone
Andrew Bone

Posted on

3 1

Learning GO: The container types

Welcome back to another exploration of Go! This week, we're looking at Go’s three container types: arrays, slices, and maps.

gopher with conveyor belt

So far, things have been quite easy to follow, but these examples are getting longer and denser. Let’s dive in and make sense of it all.

Arrays

In Go, an array is a sequence of elements with a fixed length. Each element must be of the same type.

var a [5]int  
Enter fullscreen mode Exit fullscreen mode

Here, we declare an array with a length of 5, meaning it will always have exactly five elements.

a[4] = 100  
Enter fullscreen mode Exit fullscreen mode

We can modify a specific index of an array using square brackets. Here, we’re setting the value at the fourth position (index 4).

fmt.Println("len:", len(a))  
Enter fullscreen mode Exit fullscreen mode

To get the length of an array, we use the built-in len function.

b := [5]int{1, 2, 3, 4, 5}  
Enter fullscreen mode Exit fullscreen mode

We can initialise an array while declaring it using curly braces.

b := [...]int{1, 2, 3, 4, 5}  
Enter fullscreen mode Exit fullscreen mode

By using [...], we allow Go to infer the length based on the initial values.

b := [...]int{100, 3: 400, 500}  
Enter fullscreen mode Exit fullscreen mode

Interestingly, we can also specify an index while declaring values. Here, b[3] is 400, and b[4] is 500, leaving b[1] and b[2] as zero values.

Multi-Dimensional Arrays

var twoD [2][3]int  
for i := 0; i < 2; i++ {  
    for j := 0; j < 3; j++ {  
        twoD[i][j] = i + j  
    }  
}  
Enter fullscreen mode Exit fullscreen mode

Go supports multi-dimensional arrays. Here, we create a 2×3 array and populate it using nested loops.

twoD := [2][3]int{  
    {1, 2, 3},  
    {4, 5, 6},  
}  
Enter fullscreen mode Exit fullscreen mode

Alternatively, we can declare and initialise a multi-dimensional array directly.

Slices

Slices and arrays have a lot in common, but one key difference is that a slice doesn't require its length to be known at build time, it can grow dynamically.

var s []string  
Enter fullscreen mode Exit fullscreen mode

This looks familiar from the array example, except we’re not declaring the length. The s slice starts with a length of 0 and is equal to nil.

s = make([]string, 3)  

s[0] = "a"  
s[1] = "b"  
s[2] = "c"  
Enter fullscreen mode Exit fullscreen mode

Here, we use the make function to create a slice with an initial length of 3. Each index starts with a zero-valued string, and we can then assign values like a regular array.

NOTE: We can give a slice a capacity, which is its maximum length. By default, this is the same as the length passed into make, but if we pass in a second number, we can change it.

s = append(s, "d")  
s = append(s, "e", "f")  
Enter fullscreen mode Exit fullscreen mode

The append function allows us to expand a slice by adding new elements at the end.

c := make([]string, len(s))  
copy(c, s)  
Enter fullscreen mode Exit fullscreen mode

Here, we create a new slice of the same length as s using make and copy the contents over using copy.

l := s[2:5]  
l = s[:5]  
l = s[2:]  
Enter fullscreen mode Exit fullscreen mode

Slices support the slice operator [:], which allows extracting a portion of the slice between two indexes.

  • s[2:5] gives elements from index 2 to 4 (index 5 is excluded).
  • s[:5] is equivalent to s[0:5].
  • s[2:] is equivalent to s[2:len(s)].
t := []string{"g", "h", "i"}  
t2 := []string{"g", "h", "i"}  

if slices.Equal(t, t2) {  
    fmt.Println("t == t2")  
}  
Enter fullscreen mode Exit fullscreen mode

We imported the slices package at the beginning, which allows us to compare slices using slices.Equal.

twoD := make([][]int, 3)  
for i := 0; i < 3; i++ {  
    innerLen := i + 1  
    twoD[i] = make([]int, innerLen)  
    for j := 0; j < innerLen; j++ {  
        twoD[i][j] = i + j  
    }  
}  
fmt.Println("2d: ", twoD)  
Enter fullscreen mode Exit fullscreen mode

This took a minute to get my head around, so I'll break it down:

  1. We create a slice with 3 inner slices ([[], [], []]).
  2. Each inner slice is initialised with a length of i + 1 ([[int], [int, int], [int, int, int]]).
  3. Finally, we populate each inner slice by adding i + j at each position ([[0], [1, 2], [2, 3, 4]]).

Maps

Let's move on to maps. You might know them as hashes, dicts, or objects in other languages, but here they're called maps. They allow you to have a key/value pair.

m := make(map[string]int)  
Enter fullscreen mode Exit fullscreen mode

Declaring a map is similar, but this time we place the key type in square brackets [string] and the value type outside int.

m["k1"] = 7  
m["k2"] = 13  
Enter fullscreen mode Exit fullscreen mode

We set values the same way as an array or slice, but this time the keys are strings rather than indexes.

v1 := m["k1"]  
v3 := m["k3"]  
Enter fullscreen mode Exit fullscreen mode

We can retrieve values the same way we set them. If we attempt to read a key that hasn't been set, we get a zero-valued response instead of an error.

fmt.Println("len:", len(m))  
Enter fullscreen mode Exit fullscreen mode

The len function still works here and will return how many keys we have set.

delete(m, "k2")  
clear(m)  
Enter fullscreen mode Exit fullscreen mode

We have a nice delete function that removes a single key and its value, and a clear function that removes all key-value pairs.

_, prs := m["k2"]  
Enter fullscreen mode Exit fullscreen mode

When reading a value from a map, we receive two values. The first is the stored value (or a zero-valued response if the key is missing). The second is a boolean indicating whether the key was present in the map.

n := map[string]int{"foo": 1, "bar": 2}  
n2 := map[string]int{"foo": 1, "bar": 2}  

if maps.Equal(n, n2) {  
    fmt.Println("n == n2")  
}  
Enter fullscreen mode Exit fullscreen mode

Again, in this example, we've imported another package, maps, which lets us check if two maps are equivalent.

Wrapping Up

OK, it feels like we're starting to get somewhere now. We're actually learning things that could be useful in real-world applications. Next time, we'll look at functions and what we can do with them.

Thanks for reading! If you have any questions, leave a comment. I'm also considering writing a simple script, let me know if you have any ideas!

If you’d like to connect, here are my Twitter, BlueSky, and LinkedIn profiles. Come say hi 😊.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Eliminate Context Switching and Maximize Productivity

Pieces.app

Pieces Copilot is your personalized workflow assistant, working alongside your favorite apps. Ask questions about entire repositories, generate contextualized code, save and reuse useful snippets, and streamline your development process.

Learn more

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay