Files
Rodin 484dc7dd07 fix: update drifted file:line citations in Go patterns
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).
2026-05-27 05:52:38 +00:00

16 KiB

Documentation Patterns in the Go Standard Library

Source: golang/go at commit 17bd5ab

1. Package Documentation (doc.go or Package Comment)

Pattern name: Package Doc Comment

Source citation: net/http/doc.go#L6, os/file.go#L5, log/slog/doc.go#L6

What it does: The first file in a package (by convention doc.go, or the main source file) starts with a // Package xxx ... comment that explains the package's purpose, key types, and typical usage patterns.

Why: This is the first thing users see in go doc <pkg> and on pkg.go.dev. It sets context, teaches the mental model, and provides copy-paste examples.

Anti-pattern: No package comment; package comment that just restates the package name ("Package http provides http"); putting documentation in README instead of code.

Code examples from source:

// net/http/doc.go:6-12
/*
Package http provides HTTP client and server implementations.

[Get], [Head], [Post], and [PostForm] make HTTP (or HTTPS) requests:

    resp, err := http.Get("http://example.com/")
    ...
*/
// os/file.go:5-43
// Package os provides a platform-independent interface to operating system
// functionality. The design is Unix-like, although the error handling is
// Go-like; failing calls return values of type error rather than error numbers.
// Often, more information is available within the error. For example,
// if a call that takes a file name fails, such as [Open] or [Stat], the error
// will include the failing file name when printed and will be of type
// [*PathError], which may be unpacked for more information.
// log/slog/doc.go:6-10
/*
Package slog provides structured logging,
in which log records include a message,
a severity level, and various other attributes
expressed as key-value pairs.
*/

2. Section Headers in Package Docs

Pattern name: # Heading in Doc Comments

Source citation: os/file.go#L37, net/http/doc.go (multiple sections)

What it does: Uses # Section Name within the package doc comment to organize long documentation into navigable sections.

Why: Large packages need structure. Section headers render as links in pkg.go.dev and provide a scannable table of contents.

Anti-pattern: Wall-of-text package docs; using === or --- (not recognized); too many sections (fragmenting simple docs).

Code example from source:

// os/file.go:37
// # Concurrency
//
// The methods of [File] correspond to file system operations. All are
// safe for concurrent use.

3. Type/Function Comment Convention

Pattern name: // TypeName verb... or // FuncName verb...

Source citation: net/http/server.go#L65 (Handler), bufio/scan.go#L14 (Scanner)

What it does: Every exported identifier's doc comment starts with the identifier name, followed by a verb phrase describing what it does or represents.

Why: go doc extracts the first sentence as a summary. Starting with the name ensures it reads correctly in both isolation (summary lists) and full context. This is enforced by convention and checked by linters.

Anti-pattern: Starting with "This function..." or "The Foo type..."; starting with articles ("A Handler is...") for functions (acceptable for types); omitting the comment entirely.

Code examples from source:

// net/http/server.go:65
// A Handler responds to an HTTP request.

// bufio/scan.go:14-17
// Scanner provides a convenient interface for reading data such as
// a file of newline-delimited lines of text.

// net/http/request.go:867
// NewRequest wraps NewRequestWithContext using context.Background.

// os/file.go:389-390
// Open opens the named file for reading.

// regexp/regexp.go:310-312
// MustCompile is like [Compile] but panics if the expression cannot be parsed.
// It simplifies safe initialization of global variables holding compiled regular
// expressions.

Pattern name: [TypeName], [Package.Symbol], [Method] Links

Source citation: net/http/server.go#L65, os/file.go#L9

What it does: Doc comments use [SymbolName] to create hyperlinks to other identifiers. These render as clickable links on pkg.go.dev.

Why: Cross-references help users navigate the API. Links are concise and don't clutter the plain-text rendering.

Anti-pattern: Using full URLs to godoc pages; not linking related types; over-linking (every mention of every type).

Code examples from source:

// net/http/server.go:65-70
// [Handler.ServeHTTP] should write reply headers and data to the [ResponseWriter]
// and then return. Returning signals that the request is finished; it
// is not valid to use the [ResponseWriter] or read from the
// [Request.Body] after or concurrently with the completion of the
// ServeHTTP call.

// os/file.go:9-11
// if a call that takes a file name fails, such as [Open] or [Stat], the error
// will include the failing file name when printed and will be of type
// [*PathError], which may be unpacked for more information.

5. Example Test Functions

Pattern name: func ExampleXxx() / func ExampleType_Method()

Source citation: regexp/example_test.go#L13, net/http/example_handle_test.go#L16

What it does: Functions named Example, ExampleXxx, or ExampleType_Method in _test.go files serve as both executable tests and documentation. They include an // Output: comment that go test verifies.

Why: Examples that compile, run, and are verified can never go stale. They appear in go doc and pkg.go.dev alongside the relevant symbol. They teach by showing real, working code.

When to Use

Triggers:

  • You have a public function/type whose usage isn't obvious from the signature alone
  • Your README examples have drifted from the actual API (broken examples in docs)
  • You want examples that appear on pkg.go.dev AND are verified by go test

Example — before:

// README.md (may be stale):
// ```go
// result := mylib.Process("input")
// fmt.Println(result.Data)
// ```
// ← compiles? who knows. API changed last month.

Example — after:

// example_test.go
func ExampleProcess() {
    result := mylib.Process("input")
    fmt.Println(result.Data)
    // Output:
    // processed: input
}
// ← go test verifies this compiles and produces the expected output

When NOT to Use

Don't write Example tests when:

  • The function signature is self-explanatory (func Max(a, b int) int doesn't need an example)
  • The example would just restate the doc comment with no additional insight
  • You're testing internal/unexported functions (examples must use the public API)
  • The output is non-deterministic (timestamps, random values, goroutine ordering)

Over-application example:

// Pointless — the signature tells you everything
func ExampleAbs() {
    fmt.Println(math.Abs(-5))
    // Output:
    // 5
}

Better alternative:

// Skip the example for trivial functions. Write examples for non-obvious behavior:
func ExampleAbs_nan() {
    fmt.Println(math.Abs(math.NaN()))
    // Output:
    // NaN
}
// ← This teaches something surprising that the signature doesn't convey

Why: Examples exist to teach usage patterns that aren't obvious from the type signature and doc comment. Trivial examples add maintenance burden without teaching anything.

Anti-pattern: Examples that don't compile; examples without Output comments (not verified); examples in README that drift from reality.

Code examples from source:

// regexp/example_test.go:13-28
func Example() {
    // Compile the expression once, usually at init time.
    // Use raw strings to avoid having to quote the backslashes.
    var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)

    fmt.Println(validID.MatchString("adam[23]"))
    fmt.Println(validID.MatchString("eve[7]"))
    fmt.Println(validID.MatchString("Job[48]"))
    fmt.Println(validID.MatchString("snakey"))
    // Output:
    // true
    // true
    // false
    // false
}
// net/http/example_handle_test.go:16-31
type countHandler struct {
    mu sync.Mutex // guards n
    n  int
}

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)
}

func ExampleHandle() {
    http.Handle("/count", new(countHandler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

6. Inline Code Examples in Doc Comments

Pattern name: Indented Code Blocks in Comments

Source citation: os/file.go#L16, time/time.go#L928

What it does: Doc comments include indented code snippets (4 spaces) that render as preformatted code blocks in godoc.

Why: Shows typical usage patterns directly in the doc comment without requiring a separate Example test function. Good for short, illustrative snippets.

Anti-pattern: Non-indented code that doesn't render as code; examples too long for inline (use Example functions instead); examples that reference unexported symbols.

Code examples from source:

// os/file.go:16-21
// Here is a simple example, opening a file and reading some of it.
//
//     file, err := os.Open("file.go") // For read access.
//     if err != nil {
//         log.Fatal(err)
//     }

// time/time.go:925-933
// To count the number of units in a [Duration], divide:
//
//     second := time.Second
//     fmt.Print(int64(second/time.Millisecond)) // prints 1000
//
// To convert an integer number of units to a Duration, multiply:
//
//     seconds := 10
//     fmt.Print(time.Duration(seconds)*time.Second) // prints 10s

7. Deprecated Annotations

Pattern name: // Deprecated: ... in Doc Comments

Source citation: net/http/server.go#L57 (ErrWriteAfterFlush), os/file.go#L93

What it does: A paragraph starting with Deprecated: marks an identifier as deprecated and explains what to use instead.

Why: Recognized by tooling (go vet, staticcheck, IDEs). Provides a migration path without breaking backward compatibility.

When to Use

Triggers:

  • You have a better replacement for an existing function but can't remove the old one (semver)
  • Users are still calling a function that has known issues or a superior alternative
  • You want IDEs to show a strikethrough and linters to warn on usage

Example — before:

// Just delete it? Breaks everyone's code.
// Leave it silently? Users never learn about the better way.

Example — after:

// ParseDuration parses a duration string.
//
// Deprecated: Use [time.ParseDuration] instead, which handles
// all standard duration formats.
func ParseDuration(s string) (time.Duration, error) {
    return time.ParseDuration(s) // delegate to the replacement
}

When NOT to Use

Don't deprecate when:

  • The function still works fine and there's no better replacement yet
  • You're deprecating to "clean up" the API without a migration path (users will just ignore it)
  • The replacement has a different behavior or isn't a drop-in substitute (document the difference instead)
  • You're on v0.x and can just remove it (pre-1.0 allows breaking changes)

Over-application example:

// Deprecated: Use NewClientV2 instead.
func NewClient(addr string) *Client { ... }

// But NewClientV2 has a completely different API:
func NewClientV2(cfg Config) (*ClientV2, error) { ... }
// Users can't just find-and-replace — this isn't a deprecation, it's a migration

Better alternative:

// NewClient creates a client with default configuration.
// For advanced configuration (TLS, timeouts, connection pooling),
// use [NewClientWithConfig] instead.
func NewClient(addr string) *Client {
    return NewClientWithConfig(Config{Addr: addr})
}

Why: Deprecation means "there's a better way to do the same thing." If the replacement requires a fundamentally different approach, provide a migration guide — don't just slap Deprecated: on it and leave users stranded.

Anti-pattern: Removing deprecated APIs (breaks semver); deprecating without suggesting an alternative; using non-standard deprecation markers.

Code example from source:

// net/http/server.go:59-62
// Deprecated: ErrWriteAfterFlush is no longer returned by
// anything in the net/http package. Callers should not
// compare errors against this variable.
ErrWriteAfterFlush = errors.New("unused")

8. Error Documentation Convention

Pattern name: "If there is an error, it will be of type [*XxxError]"

Source citation: os/file.go#L388, 406

What it does: Functions document the concrete error type they return, enabling callers to type-assert for additional context.

Why: Go's error handling relies on type assertions and errors.Is/As. Knowing the concrete type lets callers extract structured information (path, operation, underlying cause).

Anti-pattern: Returning opaque errors with no documented structure; returning different error types from the same function without documenting which.

Code example from source:

// os/file.go:388-390
// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode [O_RDONLY].
// If there is an error, it will be of type [*PathError].
func Open(name string) (*File, error) {

9. Concurrency Documentation

Pattern name: "Safe for concurrent use" / Concurrency Guarantees

Source citation: net/http/transport.go#L79, os/types.go#L17, regexp/regexp.go#L77

What it does: Doc comments explicitly state the concurrency safety of a type or note exceptions where concurrent use is not safe.

Why: Go programs are inherently concurrent. Without explicit documentation, users must guess whether a type needs external synchronization.

Anti-pattern: Leaving concurrency safety undocumented; documenting it inconsistently across methods; saying "thread-safe" (Java-ism, use "safe for concurrent use by multiple goroutines").

Code examples from source:

// net/http/transport.go:72-73
// Transports should be reused instead of created as needed.
// Transports are safe for concurrent use by multiple goroutines.

// os/types.go:17
// The methods of File are safe for concurrent use.

// regexp/regexp.go:77-79
// A Regexp is safe for concurrent use by multiple goroutines,
// except for configuration methods, such as [Regexp.Longest].