What’s new in Go 1.21

September 11, 2023
What’s new in Go 1.21

Go 1.21 was officially released a month ago, on 9 August, and I believe it’s the perfect time to provide a summary. In this post, I’ll go through the new features and provide example applications. I’ll also highlight what I find the most exciting and why. This post draws from the official Go 1.21 release notes, which you can read in full here .

ℹ️ Some topics discussed more briefly than others. This is because they are more broad and require a more thorough approach. Stay tuned for separate posts about these topics.

New built-ins

Go 1.21 brings 3 new built-in functions as a part of the language.

min and max functions

min and max are now available for getting the smallest or largest value from a fixed number of arguments, respectively. Days of defining your own for each type are over! Powered by generics, both of them work for all ordered types . Also, both of them require at least one argument.

    
  

clear function

Function clear can be used with maps and slices. For maps it removes all elements. However, for slices it works slightly different. It doesn’t remove the elements, but zeroes them instead. Be careful!

    
  

Improvements to the generic type inference

New version of Go improves the type inference of generic functions in a couple of ways:

  • Functions (generic or non-generic) can now be called with arguments that are themselves a generic functions, without specifying the type arguments.
  • You can use a generic function without an explicit instantation when it’s either assigned to a variable or returned from another function.
  • If a function takes a generic interface as an argument the type arguments of this interface can be inferred from the parameter types of the matching methods from the interface implementation passed to the function.
  • When arguments of different types are passed as parameters of the same type parameter type, type inference will use the same logic as with untyped constants (e.g. a := 1 + 2.5 , type of a is float64 ) instead of an error. min and max functions rely on this improvement.

Those changes, together with the clear description in the language spec makes the type inference more predictable. With such improvements we can expect generics in Go to get more powerful and less error-prone over time.

Experimental loop variables

Go 1.21 comes with an experimental feature, which aims to fix one of the biggest gotchas. It changes loop variables initialization from per-loop to per-iteration.

Consider the following snippet:

    
  

It’s a test for IsEven function, which runs sub-tests in parallel. If you read it carefully, you’ll notice that the second test case is wrong as 5 is not an even number . However, when you run it you’ll see that all test cases pass.

    
  

When you execute test cases in parallel, a goroutine is created for each one of them. When you use t.Run you pass a closure to it. Because all closures are bound to the same variable, all of them will use the value from the last iteration. Not only does the invalid test case pass, but also all other test cases are executed with exactly the same value - which means that we actually run only one test case.

This is a very common gotcha, both for beginners and advanced users of Go. It’s actually so common that it’s mentioned in the Common Mistakes wiki. It’s also worth noting, that this construct is known to go vet . It reports that: loop variable tc captured by func literal .

You can fix the loop variable sharing by redeclaring it inside the loop body, as such variables are not shared between the iterations.

    
  

Now, with Go 1.21 you can run your code with GOEXPERIMENT=loopvar to achieve the same:

    
  

I’m really excited to see if this becomes the standard behavior of loops in Go 1.22.

New standard library packages

Go team included a couple of new standard library packages in this release. Let’s take a closer look and see what they are.

log/slog package

Structured logging has been a part of Go ecosystem for a very long time, thanks to open source libraries like logrus or zap . If you’re not familiar with those libraries or this concept I recommend reading the official documentation . It’s really great to see a structured logger implementation in the standard library! I can definitely see it being widely adapted which means that logging will become more standarized.

slices and maps packages

The slices package is a collection of many useful functions which work with slices of any type. It contains functions that each of us most likely needed at some point and had to write from scratch, so it’s definitely worth checking out. Full documentation is available here .

Would you like to clone or merge maps? Check if two maps are equal? Or maybe, delete elements that satisfy a certain condition? All of that is possible with maps package. Read the package documentation here .

💡 Fun fact Not all functions from the exp/maps package were included in the standard library! The decision was made to reserve Keys and Values names for functions that will return an iterator - a new language feature planned for Go 1.22. You can read more about it this proposal .

cmp package

This fairly small package adds Ordered interface (which is used by e.g. slices package and min / max functions) as well as Compare and Less functions which use it. You can check the full docs here .

That’s not everything!

The things mentioned in this blog post are a curated and opinionated list of the most exciting features which Go 1.21 brings to the table. The full list of changes and improvements is definitely longer and worth reading. You can do that here . Let me know what you’re the most thrilled to see included in this version of Go!

Go