Upstream golang/go has shifted several line numbers since citations were recorded. Updated 6 citations across 3 files: - documentation.md: server.go:55-57 → 59-62 (ErrWriteAfterFlush) - documentation.md: transport.go:79-80 → 72-73 (Transports should be reused) - structs.md: client.go:31-35 → 30-34 (A Client is an HTTP client) - structs.md: client.go:59-60 → 57-58 (type Client struct) - style.md: stream.go:280 → 292 (var _ Marshaler) - style.md: stream.go:280-281 → 292-293 (var _ Marshaler/Unmarshaler) Verified against golang/go HEAD (depth=1 clone).
21 KiB
Code Style Patterns in the Go Standard Library
Source: golang/go at commit 17bd5ab
1. Naming Conventions: mixedCaps (No Underscores)
Pattern name: mixedCaps / MixedCaps
Source citation: All stdlib code (enforced by gofmt convention, documented in Effective Go)
What it does: All identifiers use mixedCaps (unexported) or MixedCaps (exported). Underscores are never used in Go names except for test helpers and generated code.
Why: Consistent casing makes code scannable. The exported/unexported distinction
is communicated solely through initial capitalization — no separate public/private
keywords needed.
Anti-pattern: snake_case names; ALL_CAPS for constants; Hungarian notation
(strName, iCount).
Code examples from source:
// net/http/server.go — exported
type Server struct { ... }
func ListenAndServe(addr string, handler Handler) error
// net/http/server.go — unexported
func (s *Server) shuttingDown() bool
const shutdownPollIntervalMax = 500 * time.Millisecond
2. Acronyms Are All-Caps
Pattern name: Acronym Capitalization
Source citation: net/http/request.go#L130 (URL), net/http/server.go#L3040 (TLSConfig), encoding/json/stream.go#L280 (JSON)
What it does: Acronyms and initialisms (URL, HTTP, ID, JSON, XML, HTML, TLS, TCP) are always fully capitalized when exported, and fully lowercased when unexported.
Why: Consistency. URL not Url, ID not Id, HTTP not Http. This
applies even mid-word: ServeHTTP, xmlEncoder, htmlEscape.
Anti-pattern: Url, Http, Json, Id — mixing cases within an acronym.
Code examples from source:
// net/http/request.go:130
URL *url.URL
// net/http/request.go:822
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
// net/http/server.go:3040
TLSConfig *tls.Config
// encoding/json/stream.go:292
var _ Marshaler = (*RawMessage)(nil)
3. File Organization by Responsibility
Pattern name: One Concept Per File
Source citation: net/http/ directory structure
What it does: Large packages split code into files by topic/type: client.go,
server.go, transport.go, request.go, response.go, cookie.go, header.go,
fs.go, doc.go. Each file is focused.
Why: Navigability. When you want to find client logic, you open client.go.
Files stay manageable sizes. Related code lives together.
Anti-pattern: One giant file with everything; splitting by access level
(public.go / private.go); splitting by method count rather than concept.
File layout from net/http/:
client.go — Client type and methods
transport.go — Transport type (low-level RoundTripper)
server.go — Server, Handler, ServeMux
request.go — Request type and parsing
response.go — Response type and reading
cookie.go — Cookie parsing and serialization
header.go — Header type and canonicalization
fs.go — FileServer, file serving
doc.go — Package documentation
clone.go — Clone helpers
method.go — HTTP method constants
pattern.go — URL pattern matching (ServeMux routing)
4. Blank Identifier for Interface Compliance
Pattern name: var _ Interface = (*Type)(nil)
Source citation: io/io.go#L645, os/file.go#L747, encoding/json/stream.go#L280
What it does: A package-level var _ InterfaceName = (*ConcreteType)(nil) declares
that the concrete type must satisfy the interface. The compiler verifies this at
build time.
Why: Catches interface drift at compile time without creating an instance. The blank identifier discards the value — this is purely a static assertion.
When to Use
Triggers:
- You've defined a type that MUST satisfy an interface (implements
io.Reader,http.Handler, etc.) - You want a compile-time guarantee that catches drift when you add/remove methods
- You're writing a package with multiple types implementing the same interface
Example — before:
type MyWriter struct{ buf bytes.Buffer }
func (w *MyWriter) Write(p []byte) (int, error) { return w.buf.Write(p) }
// Months later, someone renames Write to WriteBuf...
// No compile error — only discovered at runtime when passed as io.Writer
Example — after:
// Compile-time check: if MyWriter stops implementing io.Writer, this fails to build
var _ io.Writer = (*MyWriter)(nil)
type MyWriter struct{ buf bytes.Buffer }
func (w *MyWriter) Write(p []byte) (int, error) { return w.buf.Write(p) }
When NOT to Use
Don't use interface compliance checks when:
- The type is unexported AND only used locally (the compiler catches it at the call site anyway)
- You're asserting against an interface you don't own and it changes frequently (creates churn)
- The interface is implemented implicitly and you're just cargo-culting the pattern for every type
Over-application example:
// Every single struct gets a compliance check, even trivial unexported ones
var _ fmt.Stringer = (*internalHelper)(nil) // only used in one function in this file
var _ fmt.Stringer = (*anotherHelper)(nil) // also never passed as Stringer anywhere
Better alternative:
// Skip the check for types that are only used locally — the compiler
// will catch the issue at the point of use:
func printThing(s fmt.Stringer) { fmt.Println(s.String()) }
printThing(&internalHelper{}) // compiler error here if String() is missing
// DO use it for exported types that implement external interfaces:
var _ io.ReadWriteCloser = (*MyConnection)(nil) // users depend on this contract
Why: The pattern exists for API contracts — types that must satisfy an interface for consumers. For unexported types used only locally, the compiler already catches mismatches at the call site. Adding the check everywhere is noise.
Anti-pattern: Relying on tests to catch interface conformance; skipping the check and discovering the mismatch at runtime; using reflection.
Code examples from source:
// io/io.go:645
var _ ReaderFrom = discard{}
// os/file.go:747-750
var _ fs.StatFS = dirFS("")
var _ fs.ReadFileFS = dirFS("")
var _ fs.ReadDirFS = dirFS("")
var _ fs.ReadLinkFS = dirFS("")
// encoding/json/stream.go:292-293
var _ Marshaler = (*RawMessage)(nil)
var _ Unmarshaler = (*RawMessage)(nil)
// net/http/server.go:4071
var _ Pusher = (*timeoutWriter)(nil)
5. Named Return Values
Pattern name: Named Returns for Documentation (and Defer)
Source citation: io/io.go#L87, 100, 314, 387; os/file.go#L140, 175
What it does: Return values are given names when the names add documentary value
(clarifying which int is what) or when defer needs to modify the return value.
Why: (n int, err error) is immediately understandable — n is the byte count.
Named returns also enable defer func() { err = wrap(err) }() patterns.
Anti-pattern: Naming returns for trivial functions where the types are
self-explanatory; using named returns as implicit variables throughout the function
body (confusing naked returns); always using naked return statements.
Code examples from source:
// io/io.go:87 — Interface documentation
type Reader interface {
Read(p []byte) (n int, err error)
}
// io/io.go:100
type Writer interface {
Write(p []byte) (n int, err error)
}
// io/io.go:387 — Named return used with defer-style logic
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
// os/file.go:140 — Named return for readability
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
6. Defer for Resource Cleanup
Pattern name: defer mu.Unlock() / defer f.Close()
Source citation: net/http/server.go#L3173, net/http/example_handle_test.go#L21
What it does: Resources acquired at the top of a scope are immediately deferred
for cleanup. Mutexes are locked then immediately defer Unlock()'d.
Why: Guarantees cleanup regardless of return path (early returns, panics). Keeps the acquire/release pair visually adjacent. Reduces bugs from forgotten unlocks.
Anti-pattern: Manual unlock at each return point; deferring in a loop (deferred calls accumulate until function exit); deferring expensive operations that should run earlier.
Code examples from source:
// net/http/server.go:3171-3174
func (s *Server) Close() error {
s.inShutdown.Store(true)
s.mu.Lock()
defer s.mu.Unlock()
// ...
}
// net/http/example_handle_test.go:21-22
func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mu.Lock()
defer h.mu.Unlock()
h.n++
fmt.Fprintf(w, "count is %d\n", h.n)
}
7. Error Wrapping and Sentinel Errors
Pattern name: Sentinel Errors + Structured Error Types
Source citation: os/error.go#L14, os/error.go#L46
What it does: Package-level sentinel errors (ErrNotExist, ErrPermission) are
declared as var for use with errors.Is(). Structured error types (*PathError,
*SyscallError) carry context and implement Unwrap() for the errors chain.
Why: Enables programmatic error handling without string matching. errors.Is(err, os.ErrNotExist) works regardless of wrapping depth. Structured types let callers
extract the operation, path, or underlying syscall error.
Anti-pattern: Comparing error strings; creating unique error types for every
possible failure; not implementing Unwrap; sentinel errors as const (breaks
errors.Is for wrapped errors — use var).
Code examples from source:
// os/error.go:14-27
var (
ErrInvalid = fs.ErrInvalid // "invalid argument"
ErrPermission = fs.ErrPermission // "permission denied"
ErrExist = fs.ErrExist // "file already exists"
ErrNotExist = fs.ErrNotExist // "file does not exist"
ErrClosed = fs.ErrClosed // "file already closed"
)
// os/error.go:46
type PathError = fs.PathError
// os/error.go:49-57
type SyscallError struct {
Syscall string
Err error
}
func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }
func (e *SyscallError) Unwrap() error { return e.Err }
8. Receiver Naming: Short, Consistent, Never this/self
Pattern name: Single-Letter or Short Receiver Names
Source citation: All stdlib code; net/http/server.go uses s for Server, bufio/scan.go uses s for Scanner
What it does: Method receivers use 1–2 letter abbreviations of the type name,
consistent across all methods of that type: s for *Server, b for *Builder,
f for *File, t for *Timer.
Why: Receivers appear on every method. Short names reduce visual noise. Consistency
within a type avoids confusion. this/self are alien to Go's conventions.
Anti-pattern: this, self, me; long receiver names like server, scanner;
inconsistent receivers across methods of the same type.
Code examples from source:
// net/http/server.go
func (s *Server) ListenAndServe() error { ... }
func (s *Server) Serve(l net.Listener) error { ... }
func (s *Server) Shutdown(ctx context.Context) error { ... }
// strings/builder.go
func (b *Builder) WriteString(s string) (int, error) { ... }
func (b *Builder) String() string { ... }
func (b *Builder) Grow(n int) { ... }
// os/file.go
func (f *File) Read(b []byte) (n int, err error) { ... }
func (f *File) Name() string { ... }
9. Constants: Typed, Grouped, with iota
Pattern name: Typed Constants with iota
Source citation: crypto/crypto.go#L70, time/time.go#L936
What it does: Related constants are grouped in a const ( ... ) block using
a named type and iota for sequential values. Constants of the same type
are exhaustively listed together.
Why: Type safety (can't accidentally pass an os.Flag where a crypto.Hash is
expected). iota eliminates magic numbers. Grouping makes the full set visible.
When to Use
Triggers:
- You have a set of related values that represent distinct states or options (status codes, modes, categories)
- Raw integers would be meaningless to readers (
SetMode(3)vsSetMode(ModeAsync)) - You want the type system to prevent passing a "color" where a "direction" is expected
Example — before:
func SetLogLevel(level int) { ... }
// Caller:
SetLogLevel(3) // what does 3 mean?!
SetLogLevel(-1) // valid? who knows
Example — after:
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelInfo
LevelWarn
LevelError
)
func SetLogLevel(level LogLevel) { ... }
// Caller:
SetLogLevel(LevelWarn) // self-documenting
When NOT to Use
Don't use typed constants with iota when:
- The values have external meaning (HTTP status codes, exit codes, protocol bytes) — use explicit values
- The set is not exhaustive or will have gaps (iota assigns sequential values; gaps require explicit assignment)
- You need the constant to interoperate with untyped int APIs without casting everywhere
Over-application example:
type HTTPStatus int
const (
StatusOK HTTPStatus = iota // 0?! HTTP 200 is not 0
StatusNotFound // 1?! Should be 404
StatusServerError // 2?! Should be 500
)
Better alternative:
type HTTPStatus int
const (
StatusOK HTTPStatus = 200
StatusNotFound HTTPStatus = 404
StatusServerError HTTPStatus = 500
)
Why: iota is for sequential enumerations where the actual numeric value doesn't matter (only the distinctness matters). When values have external meaning (wire protocols, HTTP, exit codes), use explicit values.
Anti-pattern: Untyped numeric constants; separate const declarations for related
values; using raw integers in function signatures.
Code examples from source:
// crypto/crypto.go:70-85
const (
MD4 Hash = 1 + iota
MD5
SHA1
SHA224
SHA256
// ...
)
// time/time.go:934-942
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
10. Comments: Guard Clauses Over Conditions
Pattern name: // guards x Field Comments
Source citation: net/http/example_handle_test.go#L16
What it does: When a sync primitive (mutex) protects specific fields, a brief
comment documents what it guards: mu sync.Mutex // guards n.
Why: Concurrency bugs come from unclear ownership. A one-line comment makes the lock's scope obvious to every reader.
Anti-pattern: No documentation of what a lock protects; locks that protect "everything" (unclear scope); comments that restate the type.
Code example from source:
// net/http/example_handle_test.go:16-17
type countHandler struct {
mu sync.Mutex // guards n
n int
}
11. Duration Type Pattern
Pattern name: Named Type for Semantic Units
Source citation: time/time.go#L915
What it does: Duration is type Duration int64 — a named type over a primitive.
This gives it its own method set (String(), Hours(), Truncate()) and prevents
accidental mixing with raw int64 values.
Why: Semantic meaning through the type system. You can't accidentally pass
nanoseconds where seconds are expected. Methods provide conversion and formatting.
Constants like time.Second make intent clear.
When to Use
Triggers:
- A primitive type (
int,string,float64) has a specific semantic meaning in your domain - You want to attach methods (formatting, validation, arithmetic) to the value
- You've seen bugs from accidentally mixing units (
intcould be seconds, milliseconds, or nanoseconds)
Example — before:
func SetTimeout(ms int) { ... } // is this milliseconds? seconds?
func SetRetries(n int) { ... } // can't accidentally swap these... or CAN you?
SetTimeout(5) // 5 what?
SetRetries(500) // oops, swapped arguments — compiles fine!
Example — after:
type Timeout time.Duration
type RetryCount int
func SetTimeout(t Timeout) { ... }
func SetRetries(n RetryCount) { ... }
SetTimeout(Timeout(5 * time.Second)) // explicit units
SetRetries(RetryCount(3)) // can't swap — different types
When NOT to Use
Don't create named types when:
- The primitive is only used in one place (over-engineering for a single call site)
- The type would need constant casting back to the primitive for stdlib interop
- The semantic meaning is already clear from the parameter name (
func Sleep(seconds int)in a script is fine)
Over-application example:
type Port uint16
type Host string
type Path string
// Now every function that takes these needs explicit construction:
func Connect(h Host, p Port, path Path) { ... }
Connect(Host("localhost"), Port(8080), Path("/api")) // ceremony for no safety gain
Better alternative:
// For simple configurations, a struct with named fields provides clarity without type ceremony:
type Endpoint struct {
Host string
Port uint16
Path string
}
func Connect(ep Endpoint) { ... }
Connect(Endpoint{Host: "localhost", Port: 8080, Path: "/api"})
Why: Named types shine when you need methods, when confusion between units causes real bugs (seconds vs milliseconds), or when the type system should prevent mixing semantically different values of the same primitive. If you're just adding type annotations to strings that don't interact, you're adding ceremony without safety.
Anti-pattern: Using raw int64 for durations; accepting int parameters for
time intervals; mixing units (milliseconds in one place, seconds in another).
Code example from source:
// time/time.go:915
type Duration int64
// time/time.go:947-949
func (d Duration) String() string {
var arr [32]byte
n := d.format(&arr)
return string(arr[n:])
}
12. gofmt: Non-Negotiable Formatting
Pattern name: Canonical Formatting via gofmt
Source citation: Every file in the Go standard library
What it does: All Go code is formatted with gofmt. Tabs for indentation, spaces
for alignment. No style debates — the tool decides.
Why: Eliminates formatting bikesheds. All Go code looks the same regardless of author. Diffs show only semantic changes, never style changes. Tooling can parse and emit canonical code.
Anti-pattern: Manual formatting; spaces for indentation; custom alignment rules;
checking in code that gofmt would modify.
Key rules enforced by gofmt:
- Tabs for indentation
- Opening brace on the same line (
if x {) - No optional parentheses (
if x, notif (x)) - Aligned struct field tags
- One blank line between top-level declarations
- No trailing whitespace
13. Import Organization
Pattern name: Grouped Imports (stdlib / external / internal)
Source citation: net/http/server.go#L9
What it does: Imports are organized in groups separated by blank lines:
- Standard library
- External packages (golang.org/x, third-party)
- Internal packages
The goimports tool enforces this automatically.
Why: Scannable at a glance. Makes dependency provenance clear (stdlib vs. external). Reduces merge conflicts.
Code example from source:
// net/http/server.go:9-36
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
// ... more stdlib ...
"time"
_ "unsafe" // for linkname
"golang.org/x/net/http/httpguts"
)