Modules, Build and Toolchain
Everything around the code: the module graph that fixes which versions build, the one go command that compiles, formats and checks them, the artifact you ship, and the Go version your module speaks.
part ofGo (Golang)overview, primer and where to startread it →on this pageshowhide
explore
- Dependency Resolution43 questions
- Vendored Dependencies5 questions
- go.mod and the Module Graph5 questions
- Semantic Import Versioning (v2+)5 questions
- Tagging and Publishing5 questions
- Minimal Version Selection5 questions
- replace, exclude and retract5 questions
- Module Proxy, go.sum and Private Modules5 questions
- Workspaces (go.work)4 questions
- Upgrading and Graph Pruning4 questions
- The go Command30 questions
- Compiling and Installing4 questions
- Wiring Checks Into Builds4 questions
- Build Constraints and Tags5 questions
- Doc Comments4 questions
- gofmt and go vet4 questions
- go generate and go:embed4 questions
- Build Cache and Reproducibility5 questions
- Shipping Binaries13 questions
- GOOS and GOARCH Targets4 questions
- Static Linking and CGO_ENABLED5 questions
- Version Stamping with ldflags4 questions
- Analyzers and AST Rewriting9 questions
- Custom Vet Passes5 questions
- Parsing with go/ast4 questions
- Language Version Policy19 questions
- The Compatibility Promise4 questions
- Gating Features in go.mod5 questions
- GODEBUG Settings5 questions
- GOTOOLCHAIN and Version Switching5 questions
questions
114 · 5 sectionsWhat does a `replace` directive in go.mod do, and how do you point one at a local directory?
basics
~20 sA replace directive in go.mod redirects a module path to something else: either a different module at a stated version, or a directory on disk. The build then compiles that code instead of the version the proxy would serve.
What does `go mod tidy` change in a module's go.mod, and when do you run it?
basics
~20 sgo mod tidy makes the require block match the code: it adds a requirement for every module your packages import, drops requirements nothing imports any more, maintains the indirect markers, and syncs go.sum. Run it after changing imports.
Why must a Go module's path and its import paths end in /v2 once it releases v2.0.0?
basics
~20 sGo treats each major version as a separate module. From v2 on, the module line in go.mod and every import of its packages must end in /v2, so v1 and v2 can live in one build.
What does `go get -u ./...` do in a Go module, and how does `-u=patch` differ?
basics
~20 sgo get -u ./... upgrades every module behind the packages your module imports, directly or transitively, to its newest minor or patch release. go get -u=patch caps each move at the newest patch of the minor already selected.
If your go.mod requires example.com/lib v1.2.0 and v1.9.0 is now released, which version does `go build` use?
basics
~20 sThe build uses v1.2.0. Go's minimal version selection takes the highest version some go.mod in the graph actually requires, never the newest one published. A new release stays invisible to the build until a require line names it.
What does a `//go:build linux` line at the top of a Go file do, and where must it appear?
basics
~20 sIt is a build constraint. The file is compiled only when the target operating system is Linux, and is skipped entirely otherwise. The line must sit above the package clause, with a blank line after it.
What is the difference between go build, go run and go install for a main package?
basics
~20 sgo build compiles a main package and writes the executable into the current directory. go run builds it to a temporary file, runs it, then deletes the binary. go install writes the executable into GOBIN, which defaults to GOPATH/bin.
Why is a second `go build` of unchanged Go code so much faster than the first, and where does the toolchain keep what it reuses?
basics
~20 sThe go command caches compiled package output keyed by a hash of every input. Unchanged code hashes the same, so the second build reuses the stored result instead of recompiling. Running go env GOCACHE prints the directory that holds it.
What does a //go:generate comment in a Go source file do, and when does it run?
basics
~20 sA //go:generate line is just a comment; no build step reads it. Only the go generate command scans for these lines and runs each named command in the package directory. go build and go test never run them, so the generated output must be committed.
What does gofmt do to a Go source file, and why does it have no style options?
basics
~20 sgofmt reprints a Go file in one canonical layout: tabs for indentation, blanks for alignment, fixed spacing and brace placement, and import specs sorted inside each existing block. It exposes no style settings, so formatting stops being a review topic.
How do you cross-compile a Go binary for Linux arm64 from a macOS laptop?
basics
~20 sSet GOOS and GOARCH for that one build: GOOS=linux GOARCH=arm64 go build ./cmd/tool. The Go toolchain compiles for every target it supports on its own, so there is no separate cross-compiler, VM or target SDK to install.
What does go build -ldflags "-X main.version=1.4.0" do to a Go binary?
basics
~20 sIt tells the Go linker to set the package-level string variable named version in package main to 1.4.0 when the binary is linked, so the compiled program can report that version without it being hard-coded in the source.
What does building with CGO_ENABLED=0 change about a Go binary, and what do you give up?
basics
~20 sCGO_ENABLED=0 disables cgo, so no C code is compiled in and the standard library uses its pure-Go net and os/user code. The result is a self-contained static binary that needs no C library on the host.
A Go binary built on a glibc host dies on a musl host with "no such file or directory" — why?
basics
~20 sThe binary was linked with cgo enabled, so it is dynamically linked and names a glibc dynamic loader such as /lib64/ld-linux-x86-64.so.2. That path does not exist on a musl host, so exec fails and reports the missing interpreter.
Why does a Go file named watch_linux.go disappear from a windows/amd64 build?
basics
~20 sThe go tool reads the platform out of the filename: a file ending in _linux.go is compiled only when GOOS=linux. Building for Windows drops it from the package entirely, so every symbol it declared becomes undefined.
Why does go/parser.ParseFile need a token.FileSet, and what does it return?
basics
~20 sgo/parser.ParseFile returns an *ast.File, the syntax tree of one source file, plus an error. Node positions are bare integers; the token.FileSet holds each file's name and line table, so those integers decode to file, line and column.
In golang.org/x/tools/go/analysis, what is an *analysis.Analyzer made of, and how does its Run function report a finding?
basics
~20 sAn analysis.Analyzer is a struct: Name, Doc, Requires and a Run function. Run is called once per package and calls pass.Reportf with a source position for each finding. A non-nil error from Run means the analyzer itself failed.
In a custom go vet analyzer, what does pass.TypesInfo give you that the syntax tree alone cannot?
basics
~20 spass.TypesInfo is the package's *types.Info: it maps each identifier to the declaration it resolves to and each expression to its type. That is how a check recognises context.TODO whatever the import is aliased to, and ignores an unrelated TODO.
How do ast.Inspect and ast.Walk differ, and what stops a traversal descending into children?
basics
~20 sast.Walk drives an ast.Visitor whose Visit returns a visitor for the children, or nil to skip them. ast.Inspect wraps that in a func(ast.Node) bool where false skips children. Both call you again with a nil node when a subtree ends.
A custom vet analyzer every repo runs has reported nothing for a quarter. How do you prove it still fires?
basics
~20 sTreat silence as a failure until proven otherwise. Prove the matcher on an analysistest fixture with want comments, then work up the chain: analyzer linked into the shipped binary, packages actually analysed, files not hidden by build constraints, results not served from cache.
What does the `go 1.22` line in a module's go.mod declare, and is it the Go version you must have installed?
basics
~20 sThe go line names the minimum Go release the module needs and the language version its files compile under. It is a floor, not the exact compiler you run: any newer Go release builds the module fine, and still applies the older language version.
Your machine has Go 1.24 installed and go.mod says `go 1.27` — why does `go build` still succeed?
basics
~20 sBecause GOTOOLCHAIN defaults to auto: the go line in go.mod is a minimum, not a maximum, so the go command downloads a newer Go toolchain as a module and re-executes your build with it instead of failing.
What does the GODEBUG environment variable control, and how is its value formatted?
basics
~20 sGODEBUG carries comma-separated name=value settings, such as panicnil=1, that switch particular runtime and standard-library behaviours back to an older Go release's semantics. A Go binary reads them when it starts, so no rebuild is needed.
What does the Go 1 compatibility promise guarantee when you move to a newer Go toolchain?
basics
~20 sGo 1 promises that source building and running correctly under one Go 1.x release keeps building and running under later ones. It binds the language specification and the documented standard-library API, so upgrading is normally just a recompile.
A module with `go 1.21` in go.mod is built by Go 1.25 — does `for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() }` share one `i`?
basics
~20 sYes, one shared i. Per-iteration loop variables arrived in Go 1.22 and are gated on the module's go line, not on the installed release. A go 1.21 line keeps Go 1.21 semantics, so all three goroutines close over the same variable.