| name | language-go |
| description | Go idioms — error handling, concurrency, and standard library usage. Auto-load when working with .go files, go.mod, go.sum, or when the user mentions Go, Golang, goroutines, channels, interfaces, context, or error wrapping. |
Go
Errors are values
- Return
error; do not panic across package boundaries.
- Wrap with
fmt.Errorf("doing X: %w", err) to preserve the chain.
- Inspect with
errors.Is and errors.As. Never string-match messages.
- Sentinel errors (
var ErrNotFound = errors.New(...)) for expected cases; typed errors when callers need structured data.
if err != nil {
return fmt.Errorf("load user %s: %w", id, err)
}
Interfaces — small, defined by the consumer
- Define interfaces in the package that uses them, not where concrete types live.
- Single-method interfaces are normal (
io.Reader, io.Closer).
- Accept interfaces, return concrete types.
Concurrency
- A goroutine without a known termination path is a leak. Always know who stops it.
context.Context plumbs cancellation and deadlines — first parameter, named ctx.
- Channels for ownership transfer; mutexes for protecting state. A
sync.Mutex is often simpler than a goroutine.
errgroup.Group for structured concurrency with error propagation.
g, ctx := errgroup.WithContext(ctx)
for _, job := range jobs {
job := job
g.Go(func() error { return process(ctx, job) })
}
if err := g.Wait(); err != nil { return err }
Context rules
ctx is the first parameter of any blocking or IO function.
- Never store
context.Context in a struct field.
- Don't use
context.Value for required parameters — only cross-cutting concerns like request IDs.
Code generation
- A
// Code generated ... DO NOT EDIT. header means the file is output, not source. Never hand-edit it — edits are clobbered on the next run and drift from their input.
- Edit the generator's input, then regenerate with
go generate ./... (or the tool directly). Commit the regenerated file with the source change.
- Applies to
stringer, mockgen, sqlc, protoc-gen-go, and google/wire.
google/wire — detected by a wire.go tagged //go:build wireinject plus a wire_gen.go carrying the Wire DO NOT EDIT. header. To change dependency injection:
- Edit provider sets / injectors in
wire.go only — never wire_gen.go.
- Regenerate with
go generate ./... (or wire ./...), then stage the updated wire_gen.go. If go generate ./... produces no diff, wire.go likely lacks a //go:generate wire directive — run wire ./... directly.
func InitApp(ctx context.Context) (*App, error) {
wire.Build(configSet, storeSet, NewApp)
return nil, nil
}
Doc comments
- godoc — the comment starts with the symbol name and reads as one full sentence;
go doc renders it verbatim.
- One sentence covers most exported symbols; expand only for a non-obvious contract (surprising panics, blocking behavior, ownership transfer).
func Fetch(ctx context.Context, id string) (*User, error) { ... }
Tooling
- Imports:
goimports -w .
- Format:
gofmt -w . (redundant if running goimports; keep for explicit CI parity)
- Lint:
go vet ./... + golangci-lint run
- Test:
go test ./... (see Testing below)
Testing
- Table-driven tests are the default.
t.Run(name, ...) for subtests.
t.Cleanup beats defer for shared fixtures.
httptest.Server for HTTP; testing/iotest for edge IO.
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := Add(c.a, c.b); got != c.want {
t.Errorf("Add(%d,%d) = %d, want %d", c.a, c.b, got, c.want)
}
})
}
Package design
- Package name = its purpose, lower-case, no underscores.
- Avoid
util, common, helpers. Name by the domain thing it owns.
- Cyclic imports are a design smell — break with an interface in the consuming package.
Idioms cheat sheet
if err := do(); err != nil { ... } — keep errors close to their cause.
- Zero values are useful — design types so zero works.
defer for cleanup; defer in a loop accumulates.
- Exported fields beat getters/setters unless behavior needs them.
any over interface{} in new code.
- Prefer
slices, maps, cmp from stdlib over hand-rolled helpers.
Avoid
- Naked returns outside tiny functions.
panic as flow control.
- Empty interfaces in public APIs without a strong reason.
- Over-generics — use them only when they clearly reduce duplication.