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.