Why does the rule about the maximum versions of the dependencies also apply to go directives less than 1.21?

Documentation for the go directive in the go.mod file of the main module states the following:

At go 1.21 or higher:
- …
- The go line must be greater than or equal to the go line of all dependencies.
- …

Therefore, based on that, it follows that if the go directive in the go.mod file of the main module is smaller than the go directive of any imported (directly/indirectly) module by the main module, the build will fail. What is emphasized here is that this rule only applies if the go directive in the go.mod file of the main module is greater than or equal to go 1.21.0. Therefore, if the go directive in the go.mod file of the main module is set to go 1.21.0, and some module it includes in its go.mod file has go 1.22.0, the build will fail. This is all clear to me.

What confuses me is that this rule for some reason also applies if the go directive in go.mod file of the main module is set to a version smaller than 1.21.0, for example, go 1.20.0. This is contrary to what is stated in the documentation.

For example, take a look at the following example module (I made it just for demonstration purposes). It has the go directive set to go 1.22.0.

If you import that module from the main module with the go directive set to go 1.22.0, everything works as it should. On the other hand, if the go directive in the main module is set to go 1.21.0, build won’t work. This is also expected, since it breaks the requirement. What is strange is that if you set the go directive in the main module to go 1.20.0 or something lower, it still does not work and, according to documentation, it should since the above rule does not apply to go directive lower than go 1.21.0.

What’s happening here?

Btw, I am using Go toolchain 1.22.

Hello there.

It is not what emphasised here. Documentation states the version of the language, where it was implemented. It’s not about versioning in go.mod file, but the version of the compiler in use. Go has never had the opportunity to compile packages for future versions of the language. Thus, the error message looked like this:

$ go version
go version go1.20.14 linux/arm64
$ go run main.go 
github.com/leabit/moduleA/package1: cannot compile Go 1.22 code

Even tho, the go mod tidy command worked fine

Starting from go1.21, this error was changed to a more proper explanation:

$ go version
go version go1.21.7 linux/arm64
$ go mod tidy
go: finding module for package github.com/leabit/moduleA/package1
go: toolchain upgrade needed to resolve github.com/leabit/moduleA/package1
go: github.com/leabit/moduleA@v1.0.0 requires go >= 1.22.0 (running go 1.21.7; GOTOOLCHAIN=local)

Hi Leo.

Actually, Golang compiler (toolchain) was previously able to compile future versions of the language. They made the constraint about versions in Go 1.21. However, some similar constraint also exists for Go 1.19 release starting at Go 1.19.13 and Go 1.20 release starting at go 1.20.8. Specifically, higher versions (1.19 release from the patch 13 and 1.20 release from the patch 8) refuses to load modules declaring versions Go 1.22 and higher. Look at Go Toolchains - The Go Programming Language - “Module and workspace configuration” for more details.

Cite:

Before Go 1.21, Go toolchains treated the go line as an advisory requirement: if builds succeeded the toolchain assumed everything worked, and if not it printed a note about the potential version mismatch. Go 1.21 changed the go line to be a mandatory requirement instead. This behavior is partly backported to earlier language versions: Go 1.19 releases starting at Go 1.19.13 and Go 1.20 releases starting at Go 1.20.8, refuse to load workspaces or modules declaring version Go 1.22 or later.

Related to my question (referring to behavior of Go 1.22 toolchain):

After doing some research, I found that the reason I can’t have, for example, the go directive 1.20 and use modules with versions higher or equal to go 1.21 is because it is considered as a buggy behavior. Since the Go 1.21 introduced the rule about versions, even though the main module is specified to go 1.20 for which the constraint does not apply, you are still importing the versions (for example, go 1.21) for which it does apply, so Golang toolchain at 1.21 and higher (in my case 1.22) consider it as a bug and disable it. However, if you specify some lower version everything still works as it should. For example, if your main module is set to go 1.13 and you import module with version go 1.20 (any version lower than go 1.21 in which the constraint is introduced) everything works.

1 Like

Oh wow. Thx for such comprehensive investigation. Clearly changed my understanding, since I wasn’t aware about such details :+1:t2:

1 Like

The go directive in the go.mod file acts as a minimum requirement. Dependencies with a higher go directive than the main module will cause build failures.

1 Like