What does go vet check for, and how does it differ from a linter?
Quick Answer
go vet is a built-in static analysis tool that catches suspicious constructs that are likely bugs, not style issues — things like a Printf call with mismatched format verbs and arguments, copying a sync.Mutex or sync.WaitGroup by value, unreachable code, and struct tags that don't parse correctly. It ships with the Go toolchain and is considered part of a correct build pipeline; many teams run go vet ./... in CI unconditionally. A linter like golangci-lint goes further, checking style conventions, unused variables, complexity thresholds, and dozens of additional rules from multiple underlying analyzers, often including go vet's own checks as a subset. The practical distinction: go vet flags things that are very likely actual bugs; a linter additionally flags things that are stylistic or debatable, and is usually more configurable per-team.
Detailed Answer
go vet and a linter both analyze code without running it, but they aim at different problems.
A bug go vet catches
func main() {
name := "Alice"
fmt.Printf("Hello, %d\n", name) // %d expects an integer, got a string
}
go vet ./...
./main.go:4:2: Printf format %d has arg name of wrong type string
This is a real bug (it would print garbage, not crash, at runtime), and go vet catches it at build time without ever running the program.
Another example: copying a Mutex
type Counter struct {
mu sync.Mutex
n int
}
func increment(c Counter) { // BUG: value receiver copies the Mutex
c.mu.Lock()
c.n++
c.mu.Unlock()
}
./main.go:8:16: increment passes lock by value: main.Counter contains sync.Mutex
What a linter adds on top
golangci-lint run
main.go:12:2: unused variable 'x' (unused)
main.go:20:1: cyclomatic complexity 15 of function process() is high (gocyclo)
main.go:5:1: exported function Foo should have comment (golint)
These are real code-quality issues, but none of them are "this is definitely a bug" the way a go vet finding usually is — they're closer to style and maintainability guidance, and often configurable per team (some teams disable comment-requirement checks, for instance).
Practical guidance
Run go vet unconditionally in CI. It's fast, has essentially no false positives, and every finding usually indicates a genuine bug worth fixing immediately. Add a linter like golangci-lint (which typically wraps go vet plus many other analyzers) on top for broader code-quality enforcement, with rules tuned to your team's preferences.