Quick Tour of Go 1.23
Table of Content
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.