The proper setup of a Go module, as I understand it

October 17, 2022

I have been a little bit confused about some aspects of Go modules in practice for a while, and to get things more nailed down in my head, I'm writing some entries (writing entries here can be a form of talking to the duck). This is the first one, because before I understand the unusual corner cases I need to cover the normal and proper cases.

Suppose that you have a Go module, or a pre-modular Go package that would be a module if you modularized it, and this module is in a VCS repository of its own (which is the common case). As far as I know, there are two proper modern cases, one acceptable modern corner case, and one proper legacy case for your module or package.

(Go expects to find the repository for your module starting at the URL using a documented approach that allows you to redirect to another location.)

First, your module can have either no version tags or version tags that say it has major version 0 or 1. In both cases, the module's import path is '' and your go.mod should have that as the module's name in the 'module' line.

Second, your module can have a tag with a major version above 1, such as '2.1.0', but you have a normal source code layout with your code directly in This makes the module's import path be '' and your go.mod should have this as the module name in the 'module' line. This is the normal way to have a proper Go module that's above major version 1.

Third, as what I think of as a corner case your module can have a tag with a major version above 1 but put the code for that version in an appropriately named subdirectory, such as or This is still the module's import path, but now your go.mod for that major version goes in .../v2/go.mod and declares the module name as the v2 name. To avoid really confusing people, I think that the code outside of the subdirectory should be the v1 (or v0) code and thus have a go.mod file that just calls it ''.

(As far as I can tell from the documentation, Go doesn't accept version subdirectories for v0 and v1 major versions.)

Technically I think you can have the subdirectory be major version 2 and the root directory be, say, major version 3 and have it all work out with appropriate go.mod contents in the root directory and in the v2/ subdirectory. However, this is likely to be rather confusing to people, and it's possible that the Go tools don't like this setup. If you started out with a v2/ subdirectory but want to move away from version subdirectories for your next major version, I think that it would work to release a v3 major version with the v2/ subdirectory deleted from the tree (as of that version tag).

I believe that the one proper legacy case is that your package has major version 0 or 1, but no go.mod because you haven't modularized yet. In this case, the import path is ''. Go's module tooling handles the case of a non-modular package having a higher major version than v1, but that's for another entry.

It's possible to have modules be in subdirectories of the VCS repository root. In this case VCS version tags get complicated, as covered in the official documentation. However I believe the rest of it stays the same.

Ultimately all of this (and much more) is documented in the "Version control systems" section of the official documentation. If you need to know the full details, expect to spend a while reading it carefully, and possibly taking notes as you go back and forth between sections to work out how various scenarios (probably) get handled.

Written on 17 October 2022.
« What it means to see a 'bad' certificate in TLS Certificate Transparency logs
We're finally fully moving away from Apache's prefork MPM (hopefully) »

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

Last modified: Mon Oct 17 23:16:48 2022
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.