Recently I ran into a curious behavior related to the go directive in
the go.mod file. It’s well documented, but it’s nevertheless
surprising.
Go 1.11 introduced support for modules, as a way to define a collection of related Go packages. You can find details about its use and operation in the documentation. This support has continued to evolve throughout 1.12 and 1.13.
A module is defined using a go.mod file, described
here. A minimal file
looks like this:
module my-moduleThat’s enough to define the directory containing that file as a Go module and establish the module’s path.
Typically, a go.mod file will include dependencies, like this:
module my-module
require (
example.org/m v1.0.1
github.com/pkg/errors v0.8.1
)That says that specific versions of the named packages are required by this module.
The go.mod file can include a go directive, which establishes the
expected language version, like this:
module my-module
go 1.11
This directive is not required, but if it’s not specified, the version of the compiler being used is assumed.
If you use go mod init to create the go.mod file, the directive will
be added, and it will have the same version of the go tool that you are
currently using (if you use go 1.12, it will read go 1.12 and so on).
The documentation says that it “determines which language features are available when compiling the module”. That terse wording is what caught me by surprise.
Consider a simple example:
package main
import "fmt"
func main() {
fmt.Printf("The answer is %d\n", 42)
}That builds and runs fine with any version of Go, but since I used go
1.13 to create the go.mod file, it looks like this:
module version-1
go 1.13Running the program with Go 1.13, you get the expected output:
$ go version
go version go1.13 linux/amd64
$ go run .
The answer is 42
What would you expect to happen if you try to build this with go 1.12?
$ go version
go version go1.12.9 linux/amd64
$ go run .
The answer is 42
In the much more complex context where I ran into this, I was fully expecting the line to mean fail unless built using go 1.13 or later. This is how the verb used to behave with 1.11 throught 1.11.3. It was changed in 1.11.4 to be compatible with the same change introduced in 1.12.
Reading the quoted documentation again, note that it says the go verb “determines which language features are available when compiling the module”. It talks about features, not about the version of the compiler.
Consider this change to understand what that means:
package main
import "fmt"
func main() {
fmt.Printf("The answer is %d\n", 0b101010)
}the code still works fine with go 1.13. Now it does this with 1.12:
$ go version
go version go1.12.9 linux/amd64
$ go run .
# version-2
./version.go:6:36: syntax error: unexpected b101010, expecting comma or )
note: module requires Go 1.13
The compiler produces an error, because it doesn’t know about the new
syntax, and it outputs a note about requiring Go 1.13. It doesn’t “know”
which version is required, it’s simply stating the information stored in
the go.mod file.
If the go.mod file is changed to specify Go 1.12, the error remains
but the note goes away:
$ go version
go version go1.12.9 linux/amd64
$ go run .
# version-3
./version.go:6:36: syntax error: unexpected b101010, expecting comma or )
And using Go 1.13:
$ go version
go version go1.13 linux/amd64
$ go run .
# version-3
./version.go:6:35: binary literals only supported as of -lang=go1.13
The gist of it is:
- The
goverb ingo.modspecifies which language features are available to the code. This does not include compiler flags (e.g.-trimpath). - Building with an older version is possible as long as the code does not use features introduced with the newer version.
- Building with an older version will produce a syntax error if the code
uses newer features, and a hint is provided as to the version that is
required based on the contents of
go.mod. - Building with a compiler version that does support the features in the
code with a
go.modfile specifying an older version will not produce a syntax error but it will cause an error indicating the specific version required to build the code.