Go Rules
These rules come from app/rules/golang/ in ai-toolkit. They cover
the project's standards for coding style, frameworks, patterns,
security, and testing in Go. Apply them when writing or
reviewing Go code.
Go Coding Style
Naming
- MixedCaps/mixedCaps only. No underscores in Go names (except test functions).
- Exported:
PascalCase. Unexported:camelCase. Acronyms:HTTPClient,userID. - Short variable names in small scopes:
i,r,w,ctx,err. - Descriptive names in larger scopes:
userRepository,requestTimeout. - Package names: short, lowercase, singular (
auth,user, notutils,helpers).
Packages
- One package per directory. Package name = directory name.
- Avoid
util,common,helperspackages. Name by what it provides. - Keep package APIs small. Export only what consumers need.
- Use
internal/directory for packages not meant for external consumption.
Functions
- Accept interfaces, return structs.
- First parameter
ctx context.Contextif the function does I/O or may be cancelled. - Return
(result, error)tuple. Error is always last return value. - Use named return values only for documentation, not for naked returns.
- Keep functions short. If >40 lines, consider splitting.
Error Handling
- Always check errors. Never use
_to discard errors silently. - Wrap errors with context:
fmt.Errorf("fetching user %s: %w", id, err). - Use sentinel errors (
var ErrNotFound = errors.New(...)) for expected conditions. - Use
errors.Is()anderrors.As()for error checking, not type assertions.
Formatting
- Use
gofmt/goimports. No formatting debates in Go. - Use
golangci-lintwith a.golangci.ymlconfig in CI. - Use
go vetas minimum static analysis.
Struct Design
- Use struct embedding for composition, not inheritance.
- Prefer value receivers for small structs, pointer receivers for large or mutable.
- Be consistent: all methods on a type use the same receiver type.
- Use struct literals with field names:
User{Name: "Ada", Age: 30}.
Concurrency
- Do not start goroutines without a plan to stop them.
- Use
sync.WaitGrouporerrgroup.Groupto coordinate goroutines. - Use channels for communication, mutexes for state protection.
- Prefer
context.Contextfor cancellation and timeouts over manual signaling.
Go Frameworks
Standard Library HTTP
- Use
http.NewServeMux()(Go 1.22+ with method patterns) for simple APIs. - Use
http.HandlerFuncfor handlers. Compose with middleware pattern. - Use
context.Contextfromr.Context()in all handlers. - Use
http.TimeoutHandlerto prevent slow handlers from hanging.
Chi / Gorilla Mux
- Use Chi for routing with middleware chains and URL params.
- Use
chi.URLParam(r, "id")to extract path parameters. - Use middleware groups:
r.Group(func(r chi.Router) { r.Use(authMiddleware) }). - Prefer Chi over Gorilla Mux (Gorilla was archived, Chi actively maintained).
Gin / Echo
- Use Gin for high-performance APIs with built-in validation.
- Use binding tags:
binding:"required,email"on struct fields. - Use middleware for cross-cutting: logging, recovery, CORS, auth.
- Use
c.ShouldBindJSON()overc.BindJSON()to handle errors yourself.
GORM / sqlx / pgx
- Use
sqlxfor SQL-first with struct scanning (lightweight). - Use
pgxdirectly for PostgreSQL-specific features and performance. - Use GORM only when rapid prototyping outweighs SQL control.
- Always use prepared statements or parameterized queries.
- Use
sqlx.In()for dynamic IN clauses safely.
gRPC
- Define services in
.protofiles. Generate Go code withprotoc. - Use interceptors for auth, logging, and tracing (equivalent to middleware).
- Use deadlines (context timeout) on every RPC call.
- Use streaming RPCs for real-time data, unary for request-response.
Configuration
- Use
envconfigorviperfor configuration from env/files. - Use struct tags for env mapping:
envconfig:"DATABASE_URL". - Validate config at startup. Fail fast on invalid configuration.
- Use
flagpackage for CLI arguments in tools and utilities.
Observability
- Use
slog(Go 1.21+) for structured logging. Replacelogpackage. - Use OpenTelemetry for distributed tracing and metrics.
- Export metrics via Prometheus endpoint.
- Use
pproffor CPU and memory profiling in development.
Project Layout
- Follow Standard Go Project Layout:
cmd/,internal/,pkg/. - Entry points in
cmd/appname/main.go. - Business logic in
internal/. Shared libraries inpkg/. - Use
Makefilefor common tasks: build, test, lint, run.
Go Patterns
Error Handling
- Wrap errors with context at each call site:
fmt.Errorf("loading config: %w", err). - Define domain error types with
errors.New()or custom error structs. - Use
errors.Is()for sentinel errors,errors.As()for typed errors. - Return errors, do not panic. Reserve
panicfor truly unrecoverable states. - Handle errors immediately after the call. No deferred error checking.
Concurrency
- Use
errgroup.Groupfor concurrent operations that may fail. - Use
sync.Oncefor one-time initialization (singleton pattern). - Use
sync.Maponly for append-mostly maps with concurrent access. - Use buffered channels as semaphores:
sem := make(chan struct{}, maxConcurrency). - Prefer
context.WithTimeoutover manual timers for deadline management.
Interface Design
- Keep interfaces small: 1-3 methods. Compose larger interfaces from smaller ones.
- Define interfaces where they are consumed, not where they are implemented.
- Use
io.Reader,io.Writer,fmt.Stringerand standard interfaces where applicable. - Avoid returning interfaces from functions. Return concrete types.
Options Pattern
- Use functional options for constructors with many optional parameters.
- Pattern:
func WithTimeout(d time.Duration) Option { return func(c *Client) { c.timeout = d } }. - Provide sensible defaults. Options override defaults.
- Use
Optiontype alias:type Option func(*Config).
Dependency Injection
- Pass dependencies through constructor functions, not global variables.
- Accept interfaces in constructors:
func NewService(repo UserRepo) *Service. - Use
wireor manual wiring inmain()for dependency graph. - Avoid init() functions for anything other than simple registration.
Resource Management
- Use
deferfor cleanup immediately after acquiring a resource. - Use
context.Contextfor cancellation propagation across goroutines. - Close channels from the sender side, never the receiver.
- Use
sync.Poolfor frequently allocated temporary objects (buffers).
Anti-Patterns
- Global mutable state: use dependency injection instead.
interface{}/anyeverywhere: use generics (Go 1.18+) or specific types.- Goroutine leaks: always ensure goroutines can exit.
- Ignoring
context.Context: propagate it through all I/O paths. - Large interfaces: split into focused, composable pieces.
Go Security
Input Validation
- Validate all input at API boundaries. Use struct tags or manual validation.
- Use
validatorpackage for struct validation:validate:"required,email". - Parse and validate numeric IDs:
strconv.Atoi()with error checking. - Limit request body size:
http.MaxBytesReader(w, r.Body, maxBytes).
SQL Injection
- Always use parameterized queries:
db.Query("SELECT * FROM users WHERE id = $1", id). - Never concatenate user input into SQL strings.
- Use
sqlx.In()for safe dynamic IN clauses. - Use ORM query builders (GORM, Ent) for dynamic query construction.
Command Injection
- Use
exec.Command("binary", args...)with separate arguments, not shell strings. - Never use
exec.Command("sh", "-c", userInput). - Validate and sanitize file paths against traversal attacks.
- Use
filepath.Clean()and verify paths are within allowed directories.
Cryptography
- Use
crypto/randfor random values, nevermath/randfor security. - Use
bcryptorargon2for password hashing:golang.org/x/crypto/bcrypt. - Use
crypto/subtle.ConstantTimeCompare()for timing-safe comparisons. - Use
crypto/tlswithtls.Config{MinVersion: tls.VersionTLS12}.
Secrets
- Load secrets from environment variables:
os.Getenv("SECRET_KEY"). - Never hardcode secrets, tokens, or API keys in source code.
- Use
go-envconfigor similar for validated env var loading. - Use Go build tags or ldflags for build-time configuration.
HTTP Security
- Set
ReadTimeout,WriteTimeout,IdleTimeoutonhttp.Server. - Use
helmet-equivalent headers: HSTS, X-Content-Type-Options, X-Frame-Options. - Implement rate limiting with
golang.org/x/time/rateor middleware. - Use
net/httpwith TLS. Never serve production HTTP without encryption.
Concurrency Safety
- Use
sync.Mutexorsync.RWMutexfor shared mutable state. - Run
go test -racein CI to detect data races. - Avoid shared state where possible. Prefer channels for communication.
- Use
atomicpackage for simple counters and flags.
Dependencies
- Run
govulncheck ./...in CI to check for known vulnerabilities. - Use
go mod tidyto remove unused dependencies. - Pin dependencies via
go.sum. Review dependency changes in PRs. - Audit transitive dependencies. Use
go mod graphto inspect the tree.
Error Information Disclosure
- Never expose internal error messages to clients.
- Log detailed errors server-side, return generic messages to clients.
- Use error codes for machine-readable error classification.
- Do not include stack traces in production API responses.
Go Testing
Framework
- Use the standard
testingpackage. No external test frameworks required. - Use
testify/assertandtestify/requirefor readable assertions. - Use
testify/mockormockgenfor generating mocks. - Use
go test -racein CI to detect data races.
File Naming
- Test files:
*_test.goin the same package. - Black-box tests: use
package foo_testto test only exported API. - White-box tests: use
package footo test internals. - Test helpers:
testutil_test.goortestdata/directory.
Table-Driven Tests
- Use table-driven tests for functions with multiple input/output cases.
- Name each case:
{name: "empty input returns error", input: "", wantErr: true}. - Use
t.Run(tc.name, func(t *testing.T) { ... })for subtests. - Use
t.Parallel()in subtests when tests are independent.
Test Helpers
- Use
t.Helper()in helper functions for correct line reporting. - Use
t.Cleanup()for teardown instead of defer in test functions. - Use
testing.TBinterface to share helpers between tests and benchmarks. - Use
testdata/directory for test fixtures (excluded from build).
Mocking
- Define interfaces at the consumer, not the provider.
- Use
mockgento auto-generate mocks from interfaces. - Use
httptest.NewServer()for HTTP integration tests. - Use
httptest.NewRecorder()for handler unit tests.
Integration Tests
- Use build tags:
//go:build integrationto separate from unit tests. - Use
testcontainers-gofor database/service containers in tests. - Use
t.Setenv()(Go 1.17+) for environment variable testing.
Benchmarks
- Use
func BenchmarkXxx(b *testing.B)withb.Nloop. - Use
b.ResetTimer()after expensive setup. - Use
b.ReportAllocs()to track allocations. - Run:
go test -bench=. -benchmem.
Coverage
- Run:
go test -coverprofile=coverage.out ./.... - View:
go tool cover -html=coverage.out. - Set minimum coverage threshold in CI.
- Focus coverage on business logic, not generated code.