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:

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 or t.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? Try go 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 type HostLayout 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.

General information and summaries

Specific topics


Cover photo by David Wright on Unsplash

Get the Applied Go Weekly Newsletter

Stay up to date in the world of Go. Get a hand-curated, commented list of articles and projects, and more, every week right in your inbox.

By subscribing, you agree to the privacy policy.

Powered by Buttondown.

Share
Like this article? Tell your friends!