Engineering

Quick Tour of Go 1.23

As usual, a new version of Go is on the way, continuing its tradition of releasing updates every six months. However, this version has sparked some controversy. Many users are concerned about the addition of new features that would unnecessarily complicate the language.

Iterators

Iterators are the biggest feature of this version, and the main reason people are getting mad.

An iterator allows to traverse through a collection of elements (such as a list, a tree…) by reaching one element at a time without knowing the internal structure of the collection. While iterators are common in many programming languages, Go has lacked explicit support for them—until now.

In Go 1.23, an iterator looks like this:


func Iter[V any](seqs ...Seq[V]) Seq[V] {
return func (yield func (V) bool) bool {
for _, seq := range seqs {
if !seq(yield) {
return false
}
}

return true
}
}

This approach was already possible in previous Go versions, though without the new syntactic sugar. It makes Go feels a lot more functional, while making the language slightly more modern.

In my opinion, the introduction of iterators was inevitable once generics were added to Go. With generics, the language gained a way to construct containers, without an efficient way to traverse their content. Iterators solve this.

The main critics of this design are concerned about the philosophy of Go. The language was design to reject the complexity that other languages had embrassed (like C++, or Java…). There is a lot of func in this implementation, and that makes it look complex. The design decisions seem to have been motivated by the will to reduce data stored outside of the control flow, and the will to make the iterator look like a generator from other languages.

To make the function signature more readable, Go 1.23 adds a few types, like iter.Seq and iter.Seq2 to hide their function signatures, respectively func(yield func(V) bool) and func(yield func(K, V) bool). As we can see, a sequence can either pass one or two variables to yield. If you need more, you might want to try unpacking values.

I don’t really hate these design decisions as I understand the constraints behind them. I was excited by the idea of having iterators in Go, and I still look forward to using them. The syntax is rough, but it will become natural after some use.

The maps and slices packages will also make use of these new iterators. For example, it will be possible to use maps.Keys, maps.Values, and other methods. Behind the scene, these methods will use the new iterators. The reflect package will also receive a Type.CanSeq() method to find if a type can be iterated over.

Telemetry

The idea of adding telemetry in the Go toolchain was the source of a lot of anger. It was designed to be on by default, raising privacy concerns. After a lot of critics, the decision was made to make it opt-in, meaning that users would have to enable it manually. This is the right decision in my opinion, event if the initial reaction was exaggerated.

With this version, the Go toolchain will start collecting statistics about usage and bugs. If you are not working on sensitive stuff (or overly concerned about privacy), consider enabling it to help improve the language.

Linkname

In Go, there is a concept of variable export based on the capitalization of the variable name. If the first letter is a capital letter, the variable is public, and private otherwise. The go:linkname directive is an alternative solution to allow a non-exported variable to be accessed from somewhere else.


func Sleep(d Duration)

//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
// ...
}

For example, in the time package, there is a Sleep function that does not have a body. Instead, there is a go:linkname comment over the timeSleep function referencing the Sleep function. This allows the compiler to create a link between the two functions and export the timeSleep function under the Sleep function signature.

The main issue with this is that some people are using linkname to access variables that are not exported from the standard library. Obviously, this is not the best situation as it can lead to an unstable, and unmaintable code.

Go 1.23 adds some limitations to prevent the use of linkname on new functions added to the standard library. To avoid breaking existing code, access to already existing functions will not be limited, but using linkname on new objects from the standard library is no longer allowed. If you need access to something in the standard library, you’ll need to submit a PR and request it. No more free linknames.

New packages: iter, unique, structs

This update brings several new packages, some of which might seem underutilized now but could hint at future updates.

The unique package enables “interning” string values. The concept comes from other languages (like Java, Python, or Lisp), and make sure that, in a text file, the same strings will be referred to a unique pointer, preventing the creation of a lot of copies. Obviously, Go needs to make sure the strings stays immutable and are not used anymore before running the garbage collector. This is done thanks to this new package.

The structs package is also interesting. As of today, this package does nothing. However, is sets the foundation for future updates regarding the layouts of structs. This package adds the structs.HostLayout type that correspond to the current way structs work. It seems that they may want to improve the efficiency of structs layouts in the future.

Obviously, the iter package brings the new iterators types (iter.Seq, and iter.Seq2).

Update packages: time, os…

This version also comes with several updates to existing packages. For example, the time package receives some love. When using time.Timer or time.Ticker in a loop, some timers were waiting to be cleaned by the GC when defer t.Stop() was not called. This lead to an unjustified increase in memory usage. Now, they will automatically be eligible for garbage collection when they stop being referred to by the program.

The os package now has a CopyFS function that will create a copy of a io.FS/fs.FS into the local filesystem.

A lot of other packages are also receiving small updates, the complete list is accessible here.

Vincent

  • Published on Aug 11, 2024
  • 4 min. read

Tags