go mod, go fmt/vet/doc, workspaces, build, and ship.
Modules and Workspaces
We met go.mod in chapter 1. Here are the details you need for everyday work
on multi-package or multi-module projects.
The module file in detail
module github.com/yourname/myproject
go 1.23
require (
github.com/google/uuid v1.6.0
github.com/spf13/cobra v1.8.0
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
)
replace github.com/old/pkg => github.com/new/pkg v1.0.0
module— the import path of this module.go— minimum Go language version.require— direct (and indirect) dependencies.replace— override a dep with a local path or a fork. Useful for development:replace github.com/me/lib => ../lib.
Versions
Go modules use semantic versioning strictly:
v1.x.y— stable; backwards-compatible.v2+— must change the import path to end in/v2,/v3, etc. Yes, this is unusual; it's also how Go forces upgrades to be deliberate.- Pre-release:
v1.0.0-rc.1, etc. - Untagged commits: a "pseudo-version" like
v0.0.0-20240315123456-abcdef.
go get example.com/lib@v1.2.3 # specific version
go get example.com/lib@latest # latest stable
go get example.com/lib@main # a branch (becomes pseudo-version)
go get -u ./... # upgrade everything (minor versions)
Workspaces (go.work)
A workspace lets you point at multiple local modules at once — perfect
for working on a library and an app together without replace directives.
mkdir myws && cd myws
go work init ./app ./lib
That creates go.work:
go 1.23
use (
./app
./lib
)
Inside the workspace, the app always sees the local ./lib, no matter
what version its go.mod requests.
Vendoring
go mod vendor copies every dependency into a vendor/ folder inside your
project. Builds then use that folder instead of the module cache. Two reasons to
do this:
- Reproducible builds in restricted/offline environments.
- Easy code review of exactly what's shipped.
Most teams skip vendoring (the module cache + go.sum already gives
reproducibility), but it's still supported and required by some enterprises.