Go Packages and Modules
Modules are Go's dependency management system. A module is a collection of packages versioned together.
Module Lifecycle
go mod init github.com/user/project # create go.mod
go mod tidy # add missing, remove unused deps
go mod vendor # copy deps into vendor/ (optional)
go mod download # pre-fetch deps (CI caching)
go mod verify # check deps haven't been tampered with
Package Organization
For the full cmd/ / internal/ / pkg/ decision table and layout examples, see Project Layout in the parent golang skill — it's the canonical source for directory structure.
Quick rules specific to modules:
internal/is compiler-enforced: it can't be imported from outside the module boundary it sits under- One
package mainper binary incmd/ - Don't create
pkg/unless you have real external consumers
Init Functions
func init() {
// Runs automatically when package is loaded
// Before main(), after variable declarations
}
| Use init() for | Don't use init() for |
|---|---|
Registering drivers (database/sql, image) |
Complex logic or I/O |
| Setting up package-level constants from env | Anything that can fail silently |
| One-line computed defaults | Business logic |
Prefer explicit initialization in main(). init() is invisible, hard to test, and order-dependent.
Semantic Import Versioning
| Version | Import path | go.mod module path |
|---|---|---|
| v0.x.x or v1.x.x | github.com/user/lib |
module github.com/user/lib |
| v2.x.x | github.com/user/lib/v2 |
module github.com/user/lib/v2 |
| v3.x.x | github.com/user/lib/v3 |
module github.com/user/lib/v3 |
Major version changes = new import path. This allows v1 and v2 to coexist in the same build.
Replace and Exclude
// go.mod
// Local development: point to local copy
replace github.com/user/lib => ../lib
// Fork: use your fork instead
replace github.com/original/pkg => github.com/myfork/pkg v0.0.0-...
// Exclude a known-bad version
exclude github.com/user/lib v1.2.3
Remove replace directives before releasing — they are for local development only.
Anti-patterns
| Anti-pattern | Problem | Fix |
|---|---|---|
| Circular imports | Compile error, design smell | Extract shared types into a separate package |
| Deep package nesting | internal/service/order/v2/handler/ → hard to navigate |
Flatten — Go packages are flat by convention |
init() with side effects |
Hidden, untestable execution | See Init Functions — prefer explicit setup in main() |
| Vendoring without reason | Repo bloat, merge conflicts | Only vendor when reproducibility can't be achieved otherwise |
go get in scripts |
Modifies go.mod | Use go install pkg@version for tools |
Read On Demand
| Read When | File |
|---|---|
| go.mod syntax, go.sum, MVS, workspaces, proxies, private modules | Modules Deep Dive |
| cmd/, internal/, pkg/ directory decisions and layout examples | Project Layout |
Benchmark
Scenario: .benchmarks/scenarios/golang-packages-and-modules-001-module-mechanics.md · Run: 2026-08-31 · Log: .benchmarks/runs/2026-08-31/golang-packages-and-modules-001-module-mechanics.json
| Model | Without | With | Delta |
|---|---|---|---|
| claude-opus-4-8 | 100% | 83% | −17% |
| claude-sonnet-4-6 | 100% | 100% | +0% |
| claude-haiku-4-5 | 100% | 100% | +0% |
NEG (run 2026-08-31). Opus −17 (100→83): MVS version selection missed — the skill's versioning table has no MVS line (it punts to the reference). No edit this cycle (cap reached); 'MVS picks the highest required version, never auto-upgrades' is on the follow-up list. Gate per
.agents/skills/skill-optimizer/rules/release-gates.md.