Go Rules Quickstart
10 examples to get you started with Go rules - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Create a module:
mkdir rulesdemo && cd rulesdemo && go mod init example.com/rulesdemo. - Read Effective Go once, then use this page as a runnable companion.
Basic Examples
1. gofmt is non-negotiable
Canonical formatting is the first rule every Go repo shares.
gofmt -w .
test -z "$(gofmt -l .)" || echo "files need formatting"package main
import "fmt"
func main() {fmt.Println("before gofmt")}gofmtremoves formatting debate from code review entirely.- CI should fail when
gofmt -lprints any path. - There is no per-project style configuration to maintain.
Related: Effective Go as a Living Standard - how official guidance layers with team rules
2. Check every error return
Go treats errors as values; ignoring them violates core idioms.
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("config.yaml")
if err != nil {
fmt.Fprintf(os.Stderr, "open config: %v\n", err)
os.Exit(1)
}
defer f.Close()
}- Every
errfrom a function call needs an explicit branch. - Libraries return errors;
maindecides whether to log, wrap, or exit. _discards only when you deliberately accept loss of signal.
Related: Errors as Values: Go's Error Philosophy - why errors are not exceptions
3. Wrap errors with context
Add %w when callers need errors.Is or errors.As.
package demo
import (
"errors"
"fmt"
"io"
)
var ErrNotFound = errors.New("demo: not found")
func Load(r io.Reader) error {
if r == nil {
return fmt.Errorf("load: %w", ErrNotFound)
}
return nil
}- Wrap at boundaries where you add operation context (
load,fetch user). - Sentinels stay stable; wrapped messages can include IDs.
- Do not wrap purely to change wording without new information.
Related: Error Wrapping with %w and errors.As - wrap depth and inspection
4. context.Context as the first parameter
IO and RPC functions accept cancellation and deadlines via context.
package demo
import (
"context"
"time"
)
func Fetch(ctx context.Context, id string) error {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
// ... use ctx in outbound calls
return nil
}- Name the parameter
ctx; place it first in the signature. - Pass
ctxdown the stack; do not store it in structs. context.Background()belongs inmain, tests, and top-level handlers.
Related: Concurrency Rules Checklist - cancellation and ownership rules
5. Accept interfaces, return concrete types
Keep function parameters flexible; keep return types specific.
package demo
import "io"
type Store struct{}
func (s *Store) Save(w io.Writer, data []byte) (int, error) {
return w.Write(data)
}
func NewStore() *Store { return &Store{} }- Callers pass
io.Writer,io.Reader, or small domain interfaces. - Returning
*Storeavoids surprise behavior behind unnamed interfaces. - Define interfaces beside consumers, not beside producers.
Related: API Design Rules for Go Libraries - naming and surface rules
6. Document exported symbols
Exported identifiers are a compatibility contract.
// Package demo shows minimal godoc on exports.
package demo
// Greeter formats a greeting for name.
// It returns an empty string when name is blank.
func Greeter(name string) string {
if name == "" {
return ""
}
return "hello, " + name
}- Godoc on packages and exported funcs/types is mandatory for libraries.
- Start comments with the symbol name ("Greeter ...").
- Examples in
example_test.goappear in pkg.go.dev.
Related: Effective Go Rules Checklist - full idiom list
7. Run go vet before review
Vet catches mistakes the compiler allows.
go vet ./...package main
import "fmt"
func main() {
fmt.Printf("%d", "text") // vet: format verb mismatch
}- Add vet to CI alongside tests.
- New Go releases sometimes add vet analyzers; read release notes.
- Vet complements linters; it is not a substitute for tests.
Related: Code Quality Basics - format, vet, and hooks
Intermediate Examples
8. Table-driven tests for rule examples
Encode rule behavior as data so reviewers see cases clearly.
package demo
import "testing"
func TestGreeter(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"empty", "", ""},
{"alice", "alice", "hello, alice"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Greeter(tt.in); got != tt.want {
t.Fatalf("got %q want %q", got, tt.want)
}
})
}
}- Table tests document expected behavior better than prose rules.
- Use
t.Runnames that read like checklist items. - Pair with
-racewhen tests touch goroutines.
Related: Table-Driven Tests and Subtests - test structure rules
9. internal/ for module boundaries
Hide implementation packages consumers must not import.
rulesdemo/
go.mod
demo/
public.go
internal/
storage/
storage.go// module root imports only public packages; internal is enforced by compiler.
import "example.com/rulesdemo/internal/storage" // fails outside moduleinternal/paths are rejected by the compiler across module boundaries.- Public API surface stays small; semver applies to exported packages.
- Prefer
internalover documentation alone for "do not import."
Related: Package Naming and internal/ Directories - layout rules
10. Measure before applying performance rules
Optimization rules assume profiling evidence.
go test -bench=. -benchmem ./...
go test -cpuprofile=cpu.prof -bench=. ./...
go tool pprof -top cpu.prof- Default to clear code; optimize hot paths shown in profiles.
- Record baseline benchmarks when claiming a speedup.
- Read Performance Rules: When to Optimize before tuning GC or allocations.
Related: Performance Rules: When to Optimize - measure-first workflow
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).