Go 1.19 updates doc formatting, changes PATH lookups, and adds new atomic types

Go 1.19 delivers small but very interesting updates to the language, the standard library, and the toolchain. Here are some of the highlights.

Doc comments support links, lists, and better headings

This is my favorite update in Go 1.19. Doc comments are invaluable for package documentation. They allow generating package documentation right from the source code, following the Single Source of Truth principle. Now they have become even better.

Support for links

Doc comments now support formatted external links as well as cross-reference links to other package documentation.

For external links, Go uses a syntax similar to Markdown's shortcut reference link format that keeps the source text readable:

// NewsFilter uses a [bloom filter] and threfore 
// may return [false positive] matches.
// 
// [bloom filter]: https://en.wikipedia.org/wiki/Bloom_filter
// [false positive]: https://en.wikipedia.org/wiki/Type_I_and_type_II_errors

Cross-references to other package documentation come in two flavors:

  • To refer to exported identifiers in the current package, use [Name] or [Name1.Name2].
  • To refer to identifiers in other packages, use [pkg], [pkg.Name1], or [pkg.Name1.Name2].
// NewsFilter uses [crypto.Rand] to generate matches if the actual matches exceed the boringness threshold.

Simple lists

Simple, un-nested lists can be created either as a bullet list or a numbered list.

In either case, the list must be indented to be identified as such. A leading blank line is not necessary.

A bullet list item starts with either *, +, -, or a Unicode bullet followed by a space or tab:

// NewsFilter can run in one of three modes:
//   - linear covalence
//   - double-flex recursive attractor grid
//   - Infinite impossibility
//
// Note: 

Numbered lists start with a decimal number followed by a period (1.) or right parenthesis (2)):

// NewsFilter gets unrecoverably instable in these cases:
//   1. Non-nil input
//   2. Negative thoughts
//   3. Fake news

Headings

A heading is now clearly defined by a leading hash/number sign (#) followed by a space, similar to a Markdown level-1 heading. The heading line must be a single line and not be indented, and a blank line must separate a heading from the subsequent paragraph.

// # NewsFilter caveats
//
// These caveats apply when running NewsFilter on any news not nodded through by ACME Corporation.

Read more about the new doc comment features in the Go Doc Comment documentation.

The comment package

Related to the doc comment updates, a new package go/doc/comments allows parsing doc comments and rendering them as a doc comment, HTML, Markdown, or plain text with a few lines of code:

var p comment.Parser
doc := p.Parse(text) // text = a doc comment without comment markers

var pr comment.Printer
os.Stdout.Write(pr.Text(doc)

See comment package - go/doc/comment - Go Packages for details.

Soft memory limits

The Go runtime now allows to set a soft memory limit that affects all memory managed by the runtime itself. (External memory sources like memory held by the OS on behalf of the Go process are not affected.)

The memory limit can be set by either calling runtime/debug.SetMemoryLimit() or by setting the environment variable GOMEMLIMIT.

The memory limit sets a maximum on the total amount of memory that the Go runtime can use. The garbage collector uses this limit to set the total heap size, and it may then run more often to keep memory usage below the limit.

The increase of GC cycles may lead to thrashing when the total memory gets close to the memory limit. For this reason the limit is only a soft limit, with no guarantees that the runtime maintains this limit under all circumstances.

Read more in the Go GC Guide.

Changes to the standard library (staff picks)

(...where "staff" = me.)

The changes to the standard library are numerous, so let me pick a few that I find particularly interesting.

New atomic types in sync/atomic

The type Value in package sync/atomic now has typed cousins: Bool, Int32, Int64, Pointer, Uint32, Uint64, and Uintptr. These new types help improving the correctness of a program.

Why is there not just a generic atomic.Atomic[T] instead of the new set of types? Russ Cox answers this in his blog post about the new memory model:

I don't see a clean way to use generics to provide just a single atomic.Atomic[T] that would let us avoid introducing Bool, Int, and so on as separate types, at least not without special cases in the compiler. And that's okay.

A possibly breaking change: PATH lookups

Go has the Go 1 Backward Compatibility Promise. Under this promise, new Go compiler releases shall not break any existing code. That is, code that was written and tested with Go 1.0 should still compile fine with Go 1.19.

However, there are exceptions. Especially, important security fixes may introduce breaking changes to Go. PATH lookups are such an exception.

In a nutshell, exec.LookPath() no longer searches for a command relative to the current directory. Instead, it returns that path along with an error.

That is,

path, err := exec.LookPath("ls")

will not return an "ls" command that it finds in the current directory or in subdirectories of it. This behavior prevents running a malicious copy of "ls" inadvertently.

Find the details in Command PATH security in Go - The Go Programming Language, but for this article, it is sufficient to know that this change may break code that calls, for example, exec.Command("newsfilter") to run the command newsfilter (or newsfilter.exe on Windows) that is located in the current directory. (Side note: exec.Command() calls exec.LookPath behind the scenes.)

If you do have such calls in your code, rewrite them as exec.Command("./newsfilter") (or ".\newfilter.exe" on Windows).

The flag package allows implementing custom types

A nice addition has arrived for the flag package. The new function TextVar() allows specifying flags for custom types. For example, you can define a flag that is parsed into a custom struct type. Or a flag for time.Time or netip.Addr types.

To accomplish this, you only need to implement enconding.TextUnmarshaler accordingly.

Instead of diving into the details here, I suggest reading this blog post instead, where the author lays out a nice and short example:

Define custom command-line flag types in Go 1.19 | by Cheikh seck | Aug, 2022 | Dev Genius

fmt can append formatted data to byte slices

The fmt package has new functions Append, Appendf, and Appendln that append arbitrarily typed data (any data, that is) to byte slices.

Example:

s := []byte{}
s = fmt.Append(s, 42)
s = fmt.Appendf(s, "-> %08d", 42)

fmt.Println(string(s))

Output:

42-> 00000042

The race detector is faster

Running Go code with the -race flag makes the executable considerably slower. With Go 1.19, the situation has improved: The new thread sanitizer v3 is typically 1.5x to 2x faster than v2. Moreover, it uses only half as much memory and supports an unlimited number of goroutines.

Conclusion

The Go 1.19 update is by far not as massive as Go 1.18 was. Nevertheless, Go 1.19 has some useful updates worth memorizing—your next project may benefit from one of them.

See the full list of changes in the Go 1.19 Release Notes - The Go Programming Language.

Happy coding!

Image based on a photo by Marten Bjork on Unsplash.

Categories: The Language