The tradeoffs of Go version behavior in go.mod module files

March 10, 2021

As covered in the official documentation, the go.mod file that contains various information about a Go module can specify a Go version. This specified version does not actually do much in practice, at least so far, and in particular older versions of Go will still attempt to build the module. This behavior and others I mentioned yesterday may strike people as peculiar or even crazy. However, any behavior here has tradeoffs and I think that Go has chosen what is a sensible option.

The core tradeoff is between what Go version is assigned to a newly created module and what older versions of Go will do with modules that name a newer version. If a newly created module gets the current version of Go and older versions of Go will not build newer modules, then your newly created module is not useful for anyone who is using an older Go version (even though there's probably nothing in the code of the module that requires the latest Go version). This encourages people to specify older versions by hand or just to keep using older Go versions to avoid accidents. It's also a recipe for projects to get annoying issue reports of 'can you change your go.mod to set an older version of Go that this works fine on (and do a new release)'.

On the other hand, if a newly created module gets an older version of Go, then you can't immediately use any new features of your version of Go in your new code without a confusing extra manual step. This at least discourages people from using new Go features they could benefit from. And if older versions of Go won't build modules with newer Go versions, people still may not be able to build your module on some Go versions that they may want to use (such as the one supplied by their operating system, which may be sadly out of date), unless the default Go version in modules is all the way back at Go 1.12 (where the 'go' directive was introduced).

(Setting the Go version back also creates a battleground of people arguing how far back it should be. No one is going to be happy.)

If you allow older versions of Go to build modules claiming to require newer versions of Go, you more or less have to not change existing behavior, only add new behavior and new features that were previously some sort of error. If you change existing behavior, older Go versions will silently mis-compile the code, possibly introducing both bugs and security issues. If the version of Go in go.mod is a hard requirement, you can change existing behavior; if a module's author increases the Go version, it's their responsibility to make sure the result really works right. However, changing existing Go behavior is at least against the spirit of the Go 1 compatibility guarantee.

In addition, Go already has a way to implement hard Go version requirements, in the form of build directives. Build directives are also per file, not per package or module, which potentially allows you to write a module that uses new Go features but will still build and work for people with older Go versions.

The path Go has chosen (partly implicitly) allows people to use new Go features immediately and without nagging in new Go code (or at least new modules) but makes a best effort attempt to let such new code work with older versions of Go. If the code genuinely needs new Go features, it will most likely fail to build on older versions of Go with actual errors, and you'll know that the go.mod requirement is a real one.

Sidebar: The engineering reason to not change existing Go behavior

If the behavior of existing valid Go code depends on the version of Go it's for, someone reading the code in the future must both know what version of Go the code is for and remember that there's a difference in behavior in that particular area of Go between versions (and between what versions). But the version of Go is only specified in the go.mod file, not even in the Go source code file you're reading, and people are bad at remembering the exact details of changed behavior or even that it exists. Mistakes and bugs would be practically guaranteed.

Written on 10 March 2021.
« Go version directives in go.mod files: some notes and crude usage numbers
What OpenSSH sshd logs when a session disconnects (on Linux) »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Wed Mar 10 00:19:05 2021
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.