Go 1.18 was just released, bringing with it generics, fuzzing, and workspaces. Those are major features people have been asking about for years, but my last post about the minor features of Go 1.18 was an unexpected hit, so I’m following up on popular demand by bring you three even more minor features of Go. Get excited for some very small improvements to Go!

1. The any type

Okay, so I lied, this one is kind of a headlining feature, although it is also quite minor in terms of implementation. In Go, the universal type has been traditionally written as interface{}, as in json.Unmarshal(data []byte, v interface{}) error. Since the introduction of type aliases in Go 1.9, it has been possible to write your own local alias for this to save typing, such as type jsonvalue = interface{}. This was never idiomatic though because interface{} was the standard name, and there was no good reason to just make up your own name for it.

In Go 1.18, however, type any = interface{} has been added to the predeclared identifiers list to make it easier to write out generics (every generic type needs an interface metatype, which in most case is just interface{}, and writing this out takes a lot of space). Now you can use it anywhere you would use interface{}, even non-generic code. If nothing else, it’s easier to type a n y than to create curly braces with shift-[ shift-] on a US keyboard, and God knows what combination of keys on various European keyboards.

2. log.Logger optimizes for io.Discard

To make up for the last feature being well known, this feature is so minor, it is not even mentioned in the release notes!

The story is that there was a Github issue about making ignoring log messages more efficient and it was decided to specially test for io.Discard and short-circuit the logging to ensure that there are no extra allocations. Now when you can switch off log.Logger by setting output to io.Discard and pay less of a price for calling it.

3. bufio.Writer.AvailableBuffer()

In Go, you can think about there being three ways of handling bytes of data or text. The normal way is to use strings. Strings are very convenient to use, but because they are immutable, they can put pressure on the garbage collector to keep track of their creation and freeing.

To reach a higher level of efficiency, you can instead use the io.Reader and io.Writer interfaces to work with copies of bytes through smaller buffer sized windows as though working with a file-like object. This is an extremely powerful idiom and makes it possible to glue together bits of Go code like Unix pipes. It is, however, less convenient than working with strings on the one hand, and still not maximally efficient on the other.

Finally, the lowest level idiom for manipulating bytes is the “AppendX” idiom. You can see this used at various places in the standard library, like strconv.AppendInt(dst []byte, i int, base int) []byte. In this idiom, instead of just using an immutable string or using a byte slice to copy to and from file-like objects, there is a slice of bytes with an existing excess capacity and the desired data gets appended to that byte slice. Go 1.18 actually introduces another AppendX function, unicode/utf8.AppendRune, and there are some new fmt.Append functions planned for Go 1.19.

Go 1.18 adds the bufio.Writer.AvailableBuffer() method to the standard library to make working efficiently with these byte slices more convenient. The docs give this example:

w := bufio.NewWriter(os.Stdout)
for _, i := range []int64{1, 2, 3, 4} {
    b := w.AvailableBuffer()
    b = strconv.AppendInt(b, i, 10)
    b = append(b, ' ')

In this case, the user borrows a buffer from the bufio.Writer, appends some data into it, and the copies that into the underlying io.Writer (which in this case is stdout). This makes it possible to do very low level text wrangling without allocating memory, and makes using the AppendX idiom about as convenient as using a normal io.Writer.

So, what are your favorite features of Go 1.18 that won’t make headlines? Sound off in the comments or on Twitter.