Golang Release 1.21: maps
- 8 minutes read - 1568 wordsNew package in Go standard library that makes easier to work with maps.
Not too long ago, we witnessed a new release of our favorite programming language. The Go team didn’t disappoint us once again. They introduced numerous new features, including updates to the tool command to support backward and forward compatibility. As always, the standard library has received new updates, and the first one we’ll explore in this article is the new maps package.
The new package offers only five new functions (two additional ones were removed from the package: Values and Keys),
but they provide significant value. All of them rely on Generics, a feature
introduced in Go version 1.18, which has opened up possibilities for many new features. The map package clearly provides
new tools for Go maps. In this particular case, it introduces new functions for checking map equality, deleting items from
maps, copying items into maps, and cloning maps.
Let’s dive into each of them.
Equal and EqualFunc
First, let’s examine the pair of functions used to check map equality: Equal and EqualFunc. The first one is a
straightforward function that checks the equality of two provided maps as function arguments. The second one allows
you to pass an additional argument that defines how you plan to examine the equality of values inside the maps. Here
are their signatures:
Function Equal
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool
Function EqualFunc
func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool
The Equal function is easier to understand. It simply defines two generic types, M1 and M2, which represent maps of
two other generic types, K and V. Obviously, K is for map keys, and it allows any
comparable value. The second type is V, representing map values,
and it also allows being of a comparable type.
The EqualFunc function is slightly more complicated. First, it doesn’t assume that the values in the maps are of the
same type, nor do they have to be comparable. For that reason, it introduces an additional argument, which is an equality
function for the values in the maps. This way, we can compare two maps that have the same keys but not the same values,
and we can define the logic for comparing if they are equal.
Simple usage of Equal function
first := map[string]string{
"key1": "value1",
}
second := map[string]string{
"key1": "value1",
}
fmt.Println(maps.Equal(first, second))
// Output:
// true
third := map[string]string{
"key1": "value1",
}
fourth := map[string]string{
"key1": "wrong",
}
fmt.Println(maps.Equal(third, fourth))
// Output:
// false
In the example above, there are no surprises. We use four maps to test the Equal function. In the first case, two
maps are equal, but in the second case, their values are not the same. The following example is also easy.
Additional usage of Equal function
func main() {
first := map[string]string{
"key1": "value1",
}
second := map[string]string{
"key1": "value1",
"key2": "value2",
}
fmt.Println(maps.Equal(first, second))
// Output:
// false
third := map[string]string{
"key1": "value1",
}
fourth := map[string]string{
"key1": string([]rune{'v', 'a', 'l', 'u', 'e', '1'}),
}
fmt.Println(maps.Equal(third, fourth))
// Output:
// true
}
But what will happen if we pass the second map whose types don’t match the types of the first one? Let us check that:
Function Equal and different types
first := map[string]string{
"key1": "true",
}
second := map[string]interface{}{
"key1": true,
}
fmt.Println(maps.Equal(first, second))
// Output:
// M2 (type map[string]interface{}) does not satisfy ~map[K]V
This case doesn’t even compile. In order to use the function Equal, we need to ensure that both maps are of the same
types for their keys and values. And now, this is the case where we can employ the second function, EqualFunc:
Function EqualFunc and different types
first := map[string]string{
"key1": "true",
"key2": "7",
}
second := map[string]interface{}{
"key1": true,
"key2": 7,
}
fmt.Println(maps.EqualFunc(first, second, func(v1 string, v2 interface{}) bool {
return v1 == fmt.Sprint(v2)
}))
// Output:
// true
With the EqualFunc function, we can provide a third argument, a functions that we can use to compare values of two maps.
If the keys are equal (and all keys must be present in both maps), our equality function will be called with values
belonging to the same key in both maps. Notice that the types of arguments in the equality function match the types
of the maps’ values (v1 is of type string, like the values of the first map, and v2 is of type interface{},
like the values of the second map).
Now, with these two functions, we are in a position to check the equality of two maps using the standard algorithm (where both key and value pairs must be equal), or we can provide our own algorithm for checking values’ equality (as long as the keys are equal by type and value).
Clone and Copy
The next pair of functions we want to explore are Clone and Copy. Obviously, just by looking at their names, we can
guess what they do: Clone creates an exact clone of the existing map, while Copy copies all key-value pairs from one
map to another. Let’s examine their signatures:
Function Clone
func Clone[M ~map[K]V, K comparable, V any](m M) M
Function Copy
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)
The Clone function expects one argument that should be of type M, which is a map and returns a map of exactly the
same type. This map type must have a key of type K, which is comparable, and can have any type V for values.
The Copy function has the same expectations; it handles the M1 and M2 type, which both represent a map with K
type for keys (comparable) and V type for values (any).
Let us now check the examples for Clone function:
Simple usage of function Clone
first := map[string]string{
"key1": "value1",
"key2": "value2",
}
cloned := maps.Clone(first)
fmt.Println(cloned)
// Output:
// map[key1:value1 key2:value2]
first["key1"] = "value1-change"
fmt.Println(first)
// Output:
// map[key1:value1-change key2:value2]
fmt.Println(cloned)
// Output:
// map[key1:value1 key2:value2]
In the code snippet above, we can see that the Clone function indeed creates a new instance of a map with the same
underlying types for keys and values as the original one. To ensure that we do not get a reference to the original map
(as all maps are passed as references), the example also confirms that the cloned map is completely independent of
its original. We demonstrated this by changing the original map, which did not affect the cloned one.
Simple usage of function Copy
first := map[string]string{
"key1": "value1-first",
"key2": "value2-first",
}
second := map[string]string{
"key1": "value1-second",
"key3": "value3-second",
}
maps.Copy(second, first)
fmt.Println(second)
// Output:
// map[key1:value1-first key2:value2-first key3:value3-second]
In the example above, we can see how the Copy function works. It copies all key-value pairs from one map into the other.
If both the source and destination maps have the same key, the value in the destination will be overridden by the value
from the source map. Additionally, if the destination already contains some keys that are not defined in the source,
their values will remain intact. The Copy function obviously relies on having both the source and destination maps
of the same underlying types for keys and values, as copying data between incompatible types is not possible in Go.
DeleteFunc
The last function in the new map package is DeleteFunc. We can already assume what this method does by comparing its
name to some of the functions we checked previously in the article. However, let’s first examine its signature:
Function DeleteFunc
func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool)
In the previous example, the DeleteFunc function also handles the type M, which represents a map of key-value pairs,
where the types are K for keys (comparable) and V for values (any). Additionally, besides expecting an argument
of type M, it also requires a deletion function argument. This deletion function expects arguments of types K and
V to determine if a map item should be deleted. Let’s examine the following example:
Simple usage of function DeleteFunc
holder := map[string]string{
"key1": "value1",
"key2": "value2",
"key3": "wrong",
"key4": "wrong",
}
maps.DeleteFunc(holder, func(k string, v string) bool {
return v == "wrong"
})
fmt.Println(holder)
// Output:
// map[key1:value1 key2:value2]
As we can see in the example above, the deletion function criteria are based only on the values (in this case, if the
value is equal to the string “wrong”). The result is the original map with all keys permanently deleted. But how
can we manage to delete all keys? Perhaps something like the code snippet below:
Clear all with function DeleteFunc
holder := map[string]string{
"key1": "value1",
"key2": "value2",
"key3": "wrong",
"key4": "wrong",
}
maps.DeleteFunc(holder, func(string, string) bool {
return true
})
fmt.Println(holder)
// Output:
// map[]
In this example, the deletion function simply returns true for all key-value pairs, effectively deleting all keys from
the original map. Besides this approach, we can also clear complete map by using builtin clear function:
Clear function
holder := map[string]string{
"key1": "value1",
"key2": "value2",
"key3": "wrong",
"key4": "wrong",
}
clear(holder)
fmt.Println(holder)
// Output:
// map[]
Conclusion
New version of Golang, 1.21, delivered many new updates, affecting standard library as well. In this article we checked how functions from maps packages work. Those new methods give us now possibility to easily check equality of maps, clone and copy them, and delete their items.




