Understand Go toolchain directive or your money back
With this compatibility support, the latest Go toolchain should always be the best, most secure implementation of an older version of Go.
https://go.dev/doc/go1.21#tools
You’ll never have to manually download and install a Go toolchain again. The go command will take care of it for you. https://go.dev/blog/toolchain
Go 1.21 added a new toolchain directive to go.mod. I found it challenging to fully understand its behavior, since there are several lengthy docs on the subject.
So here is a concise overview with examples:
Toolchain - the standard library as well as the compiler, assembler, and other tools.
go minimum-go-language-versionhttps://go.dev/doc/modules/gomod-ref#go
The
godirective ingo.modindicates that a module was written assuming the semantics of a given version of Go.
It affects the use of new language features, e.g. loopvar behavior, availability ofmath/rand/v2, etc…Per the release policy, critical problems are fixed by issuing patch releases.
To get fixes that are released in patch revisions, one needs to use the newer toolchain.The version must be a valid Go version, such as 1.20, 1.22.0, or 1.24rc1.
https://go.dev/ref/mod#go-mod-file-goNote that released versions of Go use the version syntax ‘1.N.P’, denoting the Pth release of Go 1.N.
The syntax
1.N(e.g.1.22) is called a “language version.” It denotes the overall family of Go releases implementing that version of the Go language and standard library.
When comparing two Go versions within a language version, the ordering from least to greatest is as follows:
For example, 1.21 < 1.21rc1 < 1.21rc2 < 1.21.0 < 1.21.1 < 1.21.2.
https://go.dev/doc/toolchain#versionAlthough there are usually no new language features in patch releases, to indicate that your module needs a released version, you must include the patch release, e.g.:
go 1.23.0This is confirmed by a member of the Go team here: https://github.com/golang/go/issues/68971#issuecomment-2300236006
Also, we can look at what others do:
127k files do not specify the patch release,
and 305k files do.toolchain minimum-toolchain-to-useThe
toolchaindirective ingo.modspecifies the minimum Go toolchain to use when working in a particular module.Go toolchains are named
goV, where V is a Go version denoting a release or release candidate. For example,go1.23.0orgo1.24rc1.
https://go.dev/doc/toolchain#nameIf the toolchain line is omitted, the module or workspace is considered to have an implicit
toolchain goVline, where V is the Go version from the go line.
So thesego.modfiles would be equivalent:go 1.23.0and
go 1.23.0 toolchain go1.23.0The go command selects the Go toolchain to use based on the
GOTOOLCHAINsetting.GOTOOLCHAIN=auto– This is the default.- The
gocommand uses its own bundled toolchain when that toolchain is at least as new as the go or toolchain lines in the main module or workspace. - When the go or toolchain line is newer than the bundled toolchain, the go command downloads and uses the specified toolchain instead.
These toolchains are packaged as special modules with the module pathgolang.org/toolchainand versionv0.0.1-goVERSION.GOOS-GOARCH. Toolchains are downloaded like any other module.
So that means, for example, if your
go.modspecifiestoolchain 1.23.5, but you have Go1.24.0installed, your binary will be built using the1.24toolchain.
I feel like this default behavior violates the Principle of least astonishment and makes it harder to achieve hermetic builds.
But, luckily, there are other settings to choose from:- The
GOTOOLCHAIN=local– automatic downloads are disabled. The local Go toolchain is used if it is >=goortoolchaindirective ingo.mod. Otherwise, the module fails to build.GOTOOLCHAIN=toolchain_version(e.g.GOTOOLCHAIN=go1.23.0) – the go command always runs that specified Go toolchain.
If a binary with that name is found in the system PATH, the go command uses it. Otherwise, the go command uses a Go toolchain it downloads and verifies.
Misc
The
goandtoolchainrequirements can be updated usinggo getlike ordinary module requirements.go get go@1.21.0go get toolchain@go1.21.0The special form
toolchain@nonemeans to remove any toolchain line, as ingo get toolchain@none
orgo get go@1.25.0 toolchain@nonego get go@latestupdates the module to require the latest released Go toolchain.Additional links
Discussion in GitHub CLI repo: https://github.com/cli/cli/issues/9489Comment from Russ Cox on the default
GOTOOLCHAINvalue in a Docker image: https://github.com/docker-library/golang/issues/472#issuecomment-1721760993https://go.dev/ref/mod
https://go.dev/doc/modules/gomod-ref
https://go.dev/doc/godebughttps://www.youtube.com/watch?v=v24wrd3RwGo
https://go.googlesource.com/proposal/+/master/design/56986-godebug.md