Go One-Two-Three: My Favorite Features of Go Release 1.23
TL;DR: Go now officially supports ranging over function iterators, adds opt-in telemetry, is WAY faster when building with Profiled Guided Optimization enabled, fixes garbage collection of tickers and timers, and provides useful updates to many packages of the standard library. Get it while it's hot!
A lot has been written about the new and changed features in Go 1.23 already, and so I aim at staying brief and link out to relevant articles and resources. I discuss the features from most to least interesting to me (although the interest levels are fairly equal across all features), but if you have different interests, just consider them randomly sorted.
Iterator functions, aka range-over-func
This is perhaps the most-discussed new feature in Go 1.23. In a nutshell, you write a custom function that receives a “yield” function as an argument. Your function then iterates over a data collection of your choice and calls the yield function on each iteration. By calling the yield function, your function passes control over to the range operator that then passes the loop key and/or value to its loop body.
Here is a quick sample code with a “custom” collection type (it's just a slice under the hood, but you can substitute any custom data container):
type MyCollection []string
func (m MyCollection) Loop(yield func(int, string) bool) {
for i, v := range m {
if !yield(i, v) {
break
}
}
}
func main() {
m := MyCollection{
"chance that",
"human has",
"noses or",
"eyes.",
}
// pre-Go 1.23: bleah, not standard loop syntax
m.Loop(func(k int, v string) bool {
fmt.Printf("%d %s ", k, v)
return true
})
fmt.Println()
// Go 1.23: mmmmh!
for i, v := range m.Loop {
fmt.Printf("%d %s ", i, v)
}
}
(Play with this code in the Go Playground.)
The standard library
got serveral additions related to iterators; namely, the iter
package and new functions in the slices
and maps
packages.
Read more about function iterators:
- A Spotlight article by me about function iterators
- Iterators in Go by John Arundel
- Go range iterators demystified | DoltHub Blog
- Exploring Go's Functional Iterators (Range-over Functions) | Perfects.Engineering Blog
- Go 1.23 Iterators Tutorial | TutorialEdge.net
Garbage collection improvements for time.Timer and co.
You may have heard of the subtle pitfalls of using time.Timer
, time.Ticker
, time.After
, or time.Tick
—for a refresher, here is a guide on
how to use timers and tickers the wrong way.
Well, I should say, pre-Go 1.23 timers and tickers.
Go 1.23 changes the implementation of channel-based timers:
- Timers and tickers are garbage-collected when they are no longer referenced. Previously, timers only got garbage-collected after they went off, and unstopped tickers never got collected. A common source of memory leaks is now a thing of the past.
- Timer channels are now unbuffered; that is, they have length 0. Prior to Go 1.23, timer channels had a capacity of 1. As a consequence, after calling either of the
t.Reset
ort.Stop
methods, it was still possible to receive a stale value from a timer channel. With Go 1.23 and later, this cannot happen anymore.
The Go Wiki describes the pre-Go-1.23 problems in more detail.
Compilation speedup with Profile Guided Optimization enabled
Enabling profile-guided optimization (PGO) easily doubled build times prior to Go 1.23. Now the build overhead should be “in the single-digit percentages”, or somewhere between 1% and 9%.
A unique package indeed: unique
The new package
unique
can canonicalize values (aka “interning” or “hash-consing”). Imagine you have a large list of (comparable) values and many of the values occur multiple times in the list. The unique package lets you create handles for each unique value and store that handle instead, possibly reducing memory consumption considerably (depending on the degree of duplication in the original data).
If that sounds too theoretical, join Matt Boyle in exploring the unique package.
//go:linkname can only refer to symbols that are marked
The //go:linkname
directive allows code inside packages that import unsafe
to create a local alias name for, and hence, access, a symbol in another package even when this symbol is unexported. With Go 1.23, the use of //go:linkname
is restricted to symbols that explicitly agree on being accessed that way.
If you own a package that uses //go:linkname
, ensure that the symbols the package refers to have been marked for use with //go:linkname
. This
happened to github.com/quic-go/quic-go, an implementation of the QUIC protocol.
Random tips
go env
too noisy? Trygo env -changed
to list only those values that differ from the default.- A struct needs to follow the host's memory layout? The new
structs
package provides a new typeHostLayout
that works as follows: “A struct with a field of type HostLayout will be laid out in memory according to host expectations, generally following the host's C ABI.” - Working with file paths? The new function
filepath.Localize
converts a slash-separated path into a valid operating system path. No more OS-specific build tags or OS detection at runtime required. - Does your middleware wrap a ResponseWriter and applies on-the-fly encoding? Be aware that this will not work in Go 1.23 anymore, due to removed HTTP headers - see this spotlight for more information.
And finally…
Do you want to help improve the Go toolchain?
Go 1.23 ships with toolchain telemetry that is totally transparent and anonymous. The collected data helps the Go team examine usage patterns and optimize the Go toolchain. You can inspect the collected data at telemetry.go.dev.
I have enabled telemetry on my Go toolchain. It is as easy as running
go telemetry on
Conclusion
Go is one of the most stable languages in terms of adding new features to the language. This, together with its “non-magic” attitude, makes Go “The Boring Language”. And yet, the community looks forward to every new release with excitement. A formidable contradiction indeed!
But maybe Go's stability is the exact reason for this excitement. We gophers know that each new feature and each enhancement is the result of a thoughtful process of weighing the pros and cons for each new or updated feature, and then meticulously working on designing and implementing this feature in the best possible way.
The output of this process is a feature update that everyone can rely on to be carefully designed, valuable, and unobtrusive. I highly appreciate Go's language maintenance philosophy and very much prefer this way over getting drowned in a flood of new language features, however silly and esoteric, just for the sake of having it in the language.
Go is small, straightforward, and powerful, and the Go core team ensures Go stays that way.
Links
General information and summaries
- Go 1.23 Release Notes - The Go Programming Language
- Go 1.23: Interactive release notes—Highly recommended! Try out function iterators, iterator functions in the
maps
andslices
packages, timer behavior, theunique
package, and more, by running live code snippets in the browser. - What's new in Go 1.23 with Carlana Johnson (Go Time #325)
- Go 1.23 in 23 minutes, a free mini-course.
Specific topics
- Spotlight: range-over-func
- Iterators in Go by John Arundel
- Go range iterators demystified | DoltHub Blog
- Exploring Go's Functional Iterators (Range-over Functions) | Perfects.Engineering Blog
- Go Wiki: Go 1.23 Timer Channel Changes - The Go Programming Language
- Release notes: iterators in the standard library
- Chris's Wiki :: The long-overdue problem coming for some people in Go 1.23
- Spotlight: ServeContent removes headers
- Go telemetry
Cover photo by David Wright on Unsplash