What does go vet check for, and how does it differ from a linter?

4 minbeginnergogo-vetlintingtooling

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.

Related Resources