3 gripes about Go modules
(Number 4 will drive you crazy)
4 min. read

Do you remember $GOPATH? It’s from the era before Go modules came to be, where installing Go libraries used to be a manual, rather frustrating experience. (This also isn’t that long ago, actually.)

Say what you will about Go modules, but compared to the $GOPATH times, they have been a literal godsend: installing, publishing, and finding packages has since become a standardised and convenient process.

So there really shouldn’t be anything to complain about. But you know how it is with everyday tools: as time goes by, we take them for granted, and at some point, the rough edges start to grate.

Here are 3 things that I wish Go modules would have done differently.

#1) The v2 debacle

Go modules are supposed to follow a semantic versioning scheme, where every breaking API change should increment the major version – e.g., from v3.4.1 to v4.0.0.

However, even many seasoned Go modules with a long and undeniable history of backwards-incompatible API changes appear to be stuck forever at version v0.x.y or v1.x.y.

To understand why that is, you can conduct a simple self-experiment: publish your own Go module and naively try to bump its major version from v1.x.y to v2.0.0. Good luck!

The developer experience of this process is a major downer (pun intended). So a lot of package maintainers – presumably for peace-of-mind reasons – appear to stay away from v2 perpetually.

If either your frustration tolerance or curiosity is over-average, make yourself a cup of coffee and dive into the nooks and crannies of publishing a v2 module. I’m not saying there isn’t good reasoning behind this, and it also isn’t rocket science in the end. However, people obviously vote with their feet and opt out collectively, so there is that.

#2) Chaos in the project root

It might mostly be me here, but this one really drives me crazy.

In many code bases (independent of language), the directory structure of the project root looks something like this:

docs/
src/
.gitignore
.editorconfig
LICENSE.txt
Makefile
README.md

A typical project root is a wild hodgepodge consisting of various config files, documentation, build manifests, utility scripts, and what have you. The one reason that makes this madness bearable is the src/ folder: Heaven’s Door into the sane and sacred land of the actual source code.

Well, unless your project is a Go module. In this case, the base reference for all imports is the project root, so you are effectively encouraged to put your .go files there as well.

That leaves you with three choices:

I would have found it nicer if the go.mod file supported a dedicated directive to specify the base reference for resolving imports. That would have been sufficiently clear, but also given maintainers more flexibility to organise their project.

#3) github.com all over the place

Doing a fulltext search for github.com in a moderately large Go project will yield a lot of matches all across the project. The reason is threefold:

package main

import "github.com/hello/world"

func main() {
	world.SayHello()
}

While it’s nice that the Go package manager encourages decentralized hosting, it’s both distracting and redundant to see the full module URLs everywhere in the code base.

Furthermore, if you were to move your module from one hosting provider to the other (e.g., from github.com to gitlab.com), then everyone would have to change all corresponding source files that reference this module.2

In my mind, the hosting location is an implementation detail, so to me it would have been more sensible to decouple and hide that information altogether. E.g., the modules’ download URLs could have been specified just once per module in the go.mod file. That way, I think the import statements would have been more concise and meaningful.


  1. See this project for an example. The import statements are still somewhat redundant, but to me it’s the best tradeoff overall. ↩︎

  2. You could try to work around this via the replace directive, but that only solves the problem temporarily. You could also provide your own proxy, but who does that? ↩︎

My e-mail is:
Copy to Clipboard