The last article about Go 1.22 you need to read

Go 1.22 is out, and so are articles, podcasts, and even a course about Go 1.22. Check the most interesting features and places for full details.

Go 1.22 was released on Februar 7th, 2024, and I bet this came as no surprise. The Go team is always careful about rolling out a new Go release, and so we had betas and release candidates to study and give feedback on. Many "What's new in Go 1.22" articles have been written days or weeks before the release date, based on the RCs available at that time.

Hence, I will not repeat everything here but rather focus on the highlights and point you to a wealth of further readings.

Highlights

Here are the updates that I consider the most interesting. (YMMV.)

New "for" loop behavior

This one is the update that many have waited for. Go 1.22 fixes a loop behavior that turned out to be a footgun for newcomers and experienced Gophers alike.

In a nutshell, the loop variable of Go <= 1.21 is a single instance across all loop iterations. If the loop body creates references to the loop variable in code that is executed later (such as goroutines or closures that are called after the loop finishes), then those references would point to the single, loop-global instance of the loop variable, rather than preserving the value the loop variable had at the particular loop iteration that created the reference.

Ok, that's pretty difficult to explain in one sentence, but you can read about the problem here in detail — just be aware that the linked playground code now works correctly and cannot serve as an example of the wrong behavior anymore, unless you switch the playground to Go 1.21 as long as this option is still available.

Starting with Go 1.22, the loop variable gets a per-iteration copy. In every loop iteration, the loop body can capture the current value of the iteration variable without having to copy it.

Range loops over integers

Talking about loops, Go 1.22 adds a "range over integers" semantic to range-style loops:

for i := range 5 {
    fmt.Print(i, " ")
}

prints 0 1 2 3 4 .

This is a nice addition to the already existing range over slices, arrays, strings, maps, and channels.

Range loops over functions

Another new range option can be examined as a Go experiment: range-over-func iterators.

How does that work? Basically, you craft

  • a func that
    • receives an object that can be iterated over
    • and returns a func that
      • receives a func for yielding values to the caller
      • and returns a boolean to indicate that the loop has finished.
      • The func that receives the yield func then iterates over the iterable object and yields a value on each iteration.

Sounds complicated? That's because it is, but the Go Wiki has a [rangefunc page](Go Wiki: Rangefunc Experiment - The Go Programming Language) that walks you through concrete examples.

New HTTP route pattern matching for ServeMux

What's your favorite third-party HTTP router? Oh, never mind. You can sunset it anyway.

net/http introduces pattern matching to the router. Whatever routing option you probably missed in previous versions of Go, ServeMux probably can do it, thanks to a flexible wildcard matching system.

The changes are not backward-compatible. In the path /appliedgo.net/{id}, for example, {id} is a wildcard in Go 1.22 but a plain string that is matched verbatim in all previous Go versions.

To get the old behavior back, set the GODEBUG environment variable to "httpmuxgo121=1" before starting the server.

Null for everyone (who works with SQL)

The database/sql package got a generic Null type for dealing with values being read from nullable database columns. Null implements the Scanner interface so that you can use it as a scan destination together with QueryRow like in this code from the sql package documentation:

var s Null[string]
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
   // use s.V
} else {
   // s is NULL 
}

(Ian Lopshire loves the Null type.)

The first "v2" package in the stdlib: "math/rand/v2"

Instead of the Read() function that already has been deprecated in math/rand, math/rand/v2 proides a generic function N that works like Int64N or Uint64N but for any integer type. If you still need math/rand.Read(), you can use the equivalent Read function in crypto/rand.

cmp.Or(): get the first non-zero value from a list of values

Do you miss a way of quickly picking a non-zero value from a list of input values? Fret no more, use cmp.Or().

cmp.Or(a, b, c) returns the first of its arguments that is not equal to the zero value of its type (or the zero value if all arguments are zero).

This can be a convenient way of providing fallback values, for example, when setting a configuration parameter from either a commandline flag, an environment variable, a configuration file value, or a default value:

port := cmp.Or(getFlag("port"), getEnvVar("PORT"), getCfgFileVal("port"), "8080")

More goodies are covered in the Go 1.22 articles I link further below.

Caveats

Reading through the Go 1.22 release notes, I spotted a few caveats worth mentioning.

http router

As mentioned above, the new matching rules cause URL paths to be interpreted differently than in older Go versions.

go get

go get no longer works outside a module in legacy GOPATH mode. So in case you still have a legacy project that needs GO111MODULE=off, keep this in mind. And reach out for help. Talk to someone. Anyone. Call the hotline for anonymous GOPATH addicts. Do what's necessary to reap the benefits of modern Go.

go test -cover

go test -cover has different output. This may be important for scripts that check the output for determining the next step.

go mod init

go mod init no longer attempts to import dependency configs from other vendoring tools.

By now, if you had a project using dep or another vendoring tool, you certainly have either moved over to Go modules a long time ago or decided to stay with your vendoring tool forever. If, however, you are late to the show, a bit of manual work should be enough to move your project to the bright side.

bufio.SplitFunc

From the release notes: "When a SplitFunc returns ErrFinalToken with a nil token, Scanner will now stop immediately. Previously, it would report a final empty token before stopping, which was usually not desired. Callers that do want to report a final empty token can do so by returning []byte{} rather than nil."

I guess this applies to rare corner cases only, but if your code logic depends on reading that final empty token, it's time for action.

On the Web

There is so much to write about the new and changed features in Go 1.22, and I definitely don't want to repeat everything that's already been written multiple times out there in the near and far corners of the Web. Rather, let me play the hub for all Go 1.22 news.

Summaries

Here are my recommendations for detailed summaries of the Go 1.22 release.

The first link is something you should definitely check out (if you haven't already — the article was published in January): it's release notes with executable (and editable!) examples, made by Anton Zhyianov. If you ask me, this should be the future of all release notes. (Powered by Anton's codapi.org, by the way.)

Go 1.22: Interactive release notes

Not interactive, but filled with examples: Percy Bolmér's Go 1.22 overview.

Exciting Go Update - v 1.22 Change Log With Examples

If you prefer listening over reading, this Go Time episode covers many of the new and updated things in Go. This episode's guest is Carlana Johnson, who has contributed to two new features of Go 1.22 (see below in "Spotlights").

What's new in Go 1.22 with Carlana Johnson (Go Time #302)

Or listen to Jonathan Hall and Shay Nehmad of the Cup O' Go podcast.

Cup o' Go | 🆕 Most of what you need to know about Go 1.22

Aaand if you prefer watching over listening and reading, check out Matt Boyle's mini course.

Go 1.22 in 22 minutes DoltHub's Jason Fulghum has shared his view of Go 1.22 back in January (that's why the title says, "coming soon").

Coming Soon: Golang 1.22 🚀 | DoltHub Blog

Last not least, the Go blog post and the official release notes are the final source of truth.

Go 1.22 is released! - The Go Programming Language

Go 1.22 Release Notes - The Go Programming Language

Spotlights

I came across a few articles that focus on particular features in Go 1.22.

Jon Calhoun sent the new ServeMux to a competition against the Chi router. His takeaway: If you already use Chi (or a similar third-party router), there is no pressure to move to ServeMux.

Go's 1.22+ ServeMux vs Chi Router - Calhoun.io

However, if you do decide to give the new ServeMux features a try (or if you are still pondering over the decision), Eli Bendersky is here to help.

Better HTTP server routing in Go 1.22 - Eli Bendersky's website

When the for loop fix was in Go experiment status, David Chase and Russ Cox published a detailed discussion of the problem and its solution.

Fixing For Loops in Go 1.22 - The Go Programming Language

Backstage insights from a Go contributor

I want to conclude this article with two blog posts that aren't normal "what's new" posts. Rather, they shed a light on the process of adding new features to Go.

Carlana Johnson has contributed reflect.TypeFor, slices.Concat, and cmp.Or to this release. In the below articles, she talks about the features from a contributor's perspective:

What’s New in Go 1.22: reflect.TypeFor · The Ethically-Trained Programmer

What’s New in Go 1.22: slices.Concat · The Ethically-Trained Programmer

Conclusion

One of the things I like about Go is that every release adds new features and improvements where needed, and nothing more. Instead of slapping on new, fancy features that make the language more complicated, the Go team takes care to only add things that make the language easier to use (like the range over ints feature).

A well-rounded release indeed.



Photos and graphics used in the opener image:


Updates: 

2024-02-11 - improved the misleading term "global loop variable", and added a link to the Cup O' Go podcast.


Categories: Releases