Making maps, slices, and arrays in GoLang is as easy as making maps, slices, and arrays in GoLang

How to Create Constant Maps, Slices, & Arrays in Golang

Boot.dev Blog ยป Golang ยป How to Create Constant Maps, Slices, & Arrays in Golang
Lane Wagner
Lane Wagner

Last published October 1, 2022

Subscribe to curated backend podcasts, videos and articles. All free.

The quick answer is that Go does not support constant arrays, maps or slices. However, there are some great workarounds.

For the most part, I’ve found that developers learning Go for the first time are pretty good at using global constants for configuration values rather than global variables. However, a problem arises when we want a constant version of some of the more complex types. The Go compiler does not allow us to create array, map, or slice constants. After realizing this, many developers decide to use a dangerous global variable.

In this article, we will explore some alternative options that allow us to make a form of constant maps, slices, and arrays, albeit with some trade-offs. Please don’t use global variables if you can avoid them!

So if Go doesn’t support these types of constants, what is the best alternative? ๐Ÿ”—

The solution is to use initialization functions. While slices, maps, and arrays once created are still able to be mutated, at least you can always get a new copy by re-calling the initialization copy. The new copy is guaranteed to contain the original values.

Example of const array in Go ๐Ÿ”—

// An initialization function that creates
// and returns a new copy of an array
func getArray() [5]int {
    return [5]int{10, 20, 30, 40, 50}
}
// A mutable array of size 3
var nums = [3]int {10, 20, 30}
// A mutable array of size 3,
// but with syntactic sugar that relies on
// the compiler to compute the length
var nums = [...]int {10, 20, 30}

Example of const slice in Go ๐Ÿ”—

// An initialization function
// that creates a new slice of strings
func getSlice() []string {
    return []string{"hello", "world"}
}

// A mutable slice of strings
var msgs = []string{"hello", "world"}

Example of const map in Go ๐Ÿ”—

// An initialization function
// that creates a map
func getMap() map[string]int {
    return map[string]int{
        "truck": 5,
        "car": 7,
    }
}

With the quick answer out of the way, let’s explore why initialization functions are our best bet.

A Brief Refresher on Globals and Constants ๐Ÿ”—

package foo

// this is a global constant
const safeRateLimit = 10

// this is a global variable
var dangerousRateLimit = 10

When setting configuration globals, which should be read-only, there’s no good reason to use a global variable. By using a variable instead of a constant you:

  • Open up the potential for bugs when you or someone else accidentally mutates the value
  • Confuse future developers who assume the value is supposed to change

Most people already know this about global variables thankfully, and switching global variables to global constants is a fairly straightforward task.

What happens if I try to use a constant array, map, or slice? ๐Ÿ”—

global slice

Let’s assume the following situation:

We have a program that needs two sets of configurations. The configurations are:

  • A list of supported social media networks
  • A rate limit for making API calls to the networks (we assume they all have the same rate limit)

Now that we know a bit about the configurations we make the following decisions:

  • Because these configurations will not change based on the environment the program is running in, we elect to set the values in code rather than using environment variables
  • Since they are needed in many places in the app, we choose to scope them globally, instead of passing them into 20+ functions
  • Because they should not change during the execution of the program, we decide to make them constant

We then write the following code:

package main

const rateLimit = 10

const supportedNetworks = []string{"facebook", "twitter", "instagram"}

Much to our surprise, when we try to compile this code we get the following error:

const initializer []string literal is not a constant

Unlike constants in JavaScript, Go doesn’t allow complex types like slices, maps, or arrays to be constant! Our first instinct may be to lazily switch it to a variable, and add a comment:

package main

const rateLimit = 10

// this is meant to be constant! Please don't mutate it!
var supportedNetworks = []string{"facebook", "twitter", "instagram"}

Whenever we find ourselves leaving comments like this, we should be aware we are doing something wrong.

The Better Solution for Constants in Go ๐Ÿ”—

It’s much better to use an initializer function as we talked about above (not to be confused with Go’s conventional init() function). An initializer function is a function that simply declares something and returns it. Like I explained above, a good solution to our problem would be as follows:

package main

const rateLimit = 10

func getSupportedNetworks() []string {
	return []string{"facebook", "twitter", "instagram"}
}

Now, anywhere in the program, we can use the result of getSupportedNetworks() and we know that there is no way we can get a mutated value.

Obviously one of the biggest downsides to this approach is that to get a new copy of the configuration you’re literally creating a new copy and making a function call. In the vast majority of cases, this should be fine - if it’s truly just a configuration you probably won’t need to be accessing it too often. That said, if you’re rapidly making new copies constantly the extra memory overhead could become a performance issue.

Good Practices ๐Ÿ”—

Being able to access to maps and slices that are effectively constant makes your code easier to read, and more importantly, less error-prone. One of the most sought-after traits of a computer scientist for high-end coding jobs is the ability to read, write, and refactor code so that it’s more maintainable and easier to understand.

Are you sure that Go doesn’t support constant maps slices and arrays? ๐Ÿ”—

Yes. From the official specification:

There are boolean constants, rune constants, integer constants, floating-point constants, complex constants, and string constants. Rune, integer, floating-point, and complex constants are collectively called numeric constants.

Find a problem with this article?

Report an issue on GitHub