Files
Rodin 82b1366a94 docs: documentation patterns from rust-lang/rust
10 patterns, 723 lines. Full spec compliance.
Patterns: doc comments, # Examples, # Safety, # Panics, # Errors,
intra-doc links, doc test attributes, module docs, // SAFETY:,
#[doc(hidden)].
2026-04-30 15:02:28 -07:00

18 KiB

Rust Documentation Patterns

Patterns for documentation in Rust, extracted from the standard library source.

Source: rust-lang/rust at commit f53b654

Stats: 133,741 doc comments, 3,912 # Examples sections, 2,864 # Safety sections, 442 # Panics sections, 139 # Errors sections, 144 doc tests in option.rs alone.


1. Doc Comments (///) on Every Public Item

Source:

library/core/src/option.rs

Every public function, struct, enum, trait, and module in the stdlib has a /// doc comment. 133,741 total across library/.

// library/core/src/option.rs
/// Converts from `&Option<T>` to `Option<&T>`.
///
/// # Examples
///
/// Calculates the length of an <code>Option<[String]></code> as an
/// <code>Option<[usize]></code> without moving the [`String`].
/// The [`map`] method takes the `self` argument by value,
/// consuming the original, so this technique uses `as_ref` to first
/// take an `Option` to a reference to the value inside the original.
///
/// [`map`]: Option::map
///
/// ```
/// let text: Option<String> = Some("Hello, world!".to_string());
/// let text_length: Option<usize> = text.as_ref().map(|s| s.len());
/// println!("still can print text: {text:?}");
/// ```
pub const fn as_ref(&self) -> Option<&T> { ... }

Why

Doc comments are compiled, tested, and rendered as HTML. They're not afterthoughts — they're part of the type system's usability. rustdoc generates browsable documentation from these. Doc tests are real tests that run in CI.

When to Use

Triggers:

  • Item is pub (visible to users)
  • ANY public API — functions, structs, enums, traits, modules, constants

Example — before:

pub fn encode(data: &[u8], key: &[u8]) -> Vec<u8> {
    // XOR encode
    data.iter().zip(key.iter().cycle()).map(|(a, b)| a ^ b).collect()
}
// No one knows what this does, what key format, what happens with empty input

Example — after:

/// XOR-encodes `data` using `key`, cycling the key as needed.
///
/// Returns a new `Vec<u8>` of the same length as `data`.
/// Encoding is symmetric: encoding the output with the same key
/// returns the original data.
///
/// # Examples
///
/// ```
/// let encoded = encode(b"hello", b"key");
/// let decoded = encode(&encoded, b"key");
/// assert_eq!(decoded, b"hello");
/// ```
///
/// # Panics
///
/// Panics if `key` is empty.
pub fn encode(data: &[u8], key: &[u8]) -> Vec<u8> { ... }

When NOT to Use

Don't use this when:

  • Item is private (use // regular comments for internal docs)
  • The function is a trait impl where the trait already documents behavior (use /// See [Trait::method]. or nothing)

2. # Examples Section (Executable Doc Tests)

Source:

3,912 # Examples sections in library/. option.rs has 144 doc code blocks, result.rs has 102.

/// # Examples
///
/// ```
/// let x: Option<u32> = Some(2);
/// assert_eq!(x.unwrap(), 2);
/// ```

Why

Doc examples are compiled and run as tests (cargo test includes doc tests). This means:

  1. Examples are always correct (they compile and pass)
  2. Breaking changes break doc tests (forces updates)
  3. Users get copy-pasteable working code

When to Use

Triggers:

  • Every public function should have at least one example
  • Complex APIs need multiple examples showing different use cases
  • The example should demonstrate the "happy path" first

Example — before:

/// Splits the string at the given delimiter.
pub fn split_at(&self, mid: usize) -> (&str, &str) { ... }
// User: "What happens at boundary? What if mid > len?"

Example — after:

/// Splits the string at byte position `mid`.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let s = "Hello, world!";
/// let (first, second) = s.split_at(7);
/// assert_eq!(first, "Hello, ");
/// assert_eq!(second, "world!");
/// ```
///
/// Splitting at the beginning and end:
///
/// ```
/// let s = "hello";
/// assert_eq!(s.split_at(0), ("", "hello"));
/// assert_eq!(s.split_at(5), ("hello", ""));
/// ```
pub fn split_at(&self, mid: usize) -> (&str, &str) { ... }

When NOT to Use

Don't use this when:

  • The function is trivially obvious (getter with no edge cases)
  • The example would require complex setup that distracts from the point (use # hidden lines or no_run instead)

3. # Safety Section (Unsafe Contract)

Source:

library/core/src/slice/mod.rs (get_unchecked)

2,864 # Safety sections in library/.

/// # Safety
///
/// Calling this method with an out-of-bounds index is
/// *[undefined behavior]* even if the resulting reference is not used.
///
/// You can think of this like `.get(index).unwrap_unchecked()`.
/// It's UB to call `.get_unchecked(len)`, even if you immediately
/// convert to a pointer.
///
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
///
/// # Examples
///
/// ```
/// let x = &[1, 2, 4];
///
/// unsafe {
///     assert_eq!(x.get_unchecked(1), &2);
/// }
/// ```
pub unsafe fn get_unchecked<I>(&self, index: I) -> &I::Output { ... }

Why

Every unsafe fn MUST document what the caller must guarantee. This is a contract: "I (the implementor) promise correctness IF you (the caller) uphold these invariants." Without this, unsafe code is unauditable.

When to Use

Triggers:

  • The function is unsafe fn
  • The function has an unsafe block with non-obvious invariants
  • A trait has unsafe methods that implementors must uphold

Example — before:

/// Gets element without bounds checking.
pub unsafe fn get_fast(&self, index: usize) -> &T { ... }
// Caller: "What are the actual requirements? Just index < len?"

Example — after:

/// Returns a reference to the element at `index` without bounds checking.
///
/// # Safety
///
/// The caller must ensure:
/// - `index < self.len()`
/// - The returned reference must not outlive the slice
/// - No mutable reference to the same element exists
///
/// Violating any of these is undefined behavior.
///
/// # Examples
///
/// ```
/// let data = &[10, 20, 30];
/// unsafe {
///     assert_eq!(*data.get_fast(1), 20);
/// }
/// ```
pub unsafe fn get_fast(&self, index: usize) -> &T { ... }

When NOT to Use

Don't use this when:

  • The function is safe (no unsafe keyword)
  • The safety requirements are documented on the trait, not repeated on every method

4. # Panics Section

Source:

442 # Panics sections in library/.

/// # Panics
///
/// Panics if `index >= self.len()`.
pub fn swap(&mut self, a: usize, b: usize) { ... }

Why

If a function can panic, users MUST know when. Panics are not errors you can catch (in normal code) — they abort the thread. Documenting them lets callers write defensive code.

When to Use

Triggers:

  • Function uses unwrap(), expect(), panic!(), or assert!()
  • Function indexes into a collection
  • Any input combination causes a panic

Example — before:

/// Divides a by b.
pub fn divide(a: f64, b: f64) -> f64 {
    assert!(b != 0.0, "division by zero");
    a / b
}
// User finds out about the panic the hard way

Example — after:

/// Divides `a` by `b`.
///
/// # Panics
///
/// Panics if `b` is zero.
///
/// # Examples
///
/// ```
/// assert_eq!(divide(10.0, 2.0), 5.0);
/// ```
///
/// ```should_panic
/// divide(1.0, 0.0);  // panics
/// ```
pub fn divide(a: f64, b: f64) -> f64 { ... }

When NOT to Use

Don't use this when:

  • The function never panics
  • Panics are impossible given the type constraints (but document the proof in a comment)

5. # Errors Section

Source:

139 # Errors sections in library/.

/// # Errors
///
/// This function will return an error if `path` does not already exist.
/// Other errors may also be returned according to [`OpenOptions::open`].
pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> { ... }

Why

When a function returns Result, document WHICH errors can occur and WHEN. This lets callers decide how to handle each case without reading the implementation.

When to Use

Triggers:

  • Function returns Result<T, E>
  • The error conditions aren't obvious from the signature
  • Different error variants have different recovery strategies

Example — before:

/// Connects to the server.
pub fn connect(addr: &str) -> Result<Connection, ConnectError> { ... }
// Which ConnectError variants? When does each happen?

Example — after:

/// Connects to the server at `addr`.
///
/// # Errors
///
/// Returns [`ConnectError::InvalidAddress`] if `addr` cannot be parsed.
/// Returns [`ConnectError::Timeout`] if connection is not established
/// within 30 seconds.
/// Returns [`ConnectError::Refused`] if the server actively refuses
/// the connection.
pub fn connect(addr: &str) -> Result<Connection, ConnectError> { ... }

When NOT to Use

Don't use this when:

  • The error type is self-documenting (io::Error with standard kinds)
  • There's only one possible error and it's obvious from the name

Source:

Thousands of intra-doc links across the stdlib.

/// See [`Option::map`] for the `Option` equivalent.
/// Unlike [`Result::unwrap`], this does not panic.
/// Returns the contained [`Ok`] value, consuming the `self` value.

Why

Intra-doc links are resolved by rustdoc into clickable hyperlinks. They're checked at compile time — broken links are warnings. This is how you create navigable documentation.

When to Use

Triggers:

  • Referencing another type, method, or trait
  • Cross-referencing related functionality
  • "See also" references

Example — before:

/// Similar to unwrap but returns a default value instead of panicking.
/// The map function can transform the value before unwrapping.
pub fn unwrap_or_default(self) -> T { ... }

Example — after:

/// Returns the contained [`Some`] value or a [`Default`] value.
///
/// Consumes `self`, then if [`Some`], returns the contained value,
/// otherwise if [`None`], returns the [default value] for `T`.
///
/// See [`unwrap_or`] for a version with a custom fallback.
/// See [`map`] to transform the contained value.
///
/// [`unwrap_or`]: Option::unwrap_or
/// [`map`]: Option::map
/// [default value]: Default::default
pub fn unwrap_or_default(self) -> T where T: Default { ... }

When NOT to Use

Don't use this when:

  • The reference is to an external crate (use full URLs)
  • The link would be more confusing than helpful
  • You're referencing something in a code block (code blocks aren't linked)

7. Doc Test Attributes (no_run, should_panic, compile_fail)

Source:

Multiple test annotations used across the stdlib:

/// ```should_panic
/// vec![1, 2, 3][99];  // panics at runtime
/// ```

/// ```compile_fail
/// let x: &str = 42;  // type error
/// ```

/// ```no_run
/// loop { /* runs forever */ }
/// ```

/// ```ignore
/// // Platform-specific, can't test in CI
/// ```

Why

Not all examples should run. should_panic proves the panic docs. compile_fail proves the type system rejects invalid code. no_run compiles but doesn't execute (for I/O, network, infinite loops). ignore skips entirely (last resort).

When to Use

Triggers:

  • should_panic: Demonstrating that invalid input causes a panic
  • compile_fail: Showing that the type system prevents misuse
  • no_run: Example requires network, file system, or blocks forever
  • ignore: Platform-specific or requires external setup

Example — before:

/// # Panics
/// Panics if index is out of bounds.
pub fn get(&self, index: usize) -> &T { ... }
// How do users know the panic really happens?

Example — after:

/// # Panics
///
/// Panics if `index >= self.len()`.
///
/// ```should_panic
/// let v = vec![1, 2, 3];
/// v[99];  // panics: index out of bounds
/// ```
pub fn get(&self, index: usize) -> &T { ... }

When NOT to Use

Don't use this when:

  • The example can run normally (just use ```)
  • You're using ignore to hide broken examples (fix them instead)

8. Module-Level Documentation (//!)

Source:

library/core/src/option.rs (module doc)

//! Optional values.
//!
//! Type [`Option`] represents an optional value: every [`Option`]
//! is either [`Some`] and contains a value, or [`None`], and
//! does not. [`Option`] types are very common in Rust code, as
//! they have a number of uses:
//!
//! * Initial values
//! * Return values for functions that are not defined
//!   over their entire input range (partial functions)
//! * ...

Why

Module docs (//!) explain the module's purpose, key types, and common patterns. They appear at the top of the rustdoc page. This is where you explain WHY the module exists, not just WHAT it contains.

When to Use

Triggers:

  • Every mod.rs or lib.rs file
  • Any module with public exports
  • When the module's purpose isn't obvious from its name

Example — before:

// src/cache/mod.rs
pub mod lru;
pub mod ttl;
pub mod eviction;
// No explanation of how these relate or which to choose

Example — after:

//! In-memory caching with configurable eviction strategies.
//!
//! This module provides three cache implementations:
//!
//! - [`lru::Cache`] — Least Recently Used eviction. Best for
//!   access-pattern-driven workloads.
//! - [`ttl::Cache`] — Time-To-Live eviction. Best for data
//!   that becomes stale after a fixed duration.
//! - [`eviction::Policy`] — Trait for custom eviction strategies.
//!
//! # Quick Start
//!
//! ```
//! use cache::lru::Cache;
//! let mut c = Cache::new(100);
//! c.insert("key", "value");
//! ```
pub mod lru;
pub mod ttl;
pub mod eviction;

When NOT to Use

Don't use this when:

  • The module contains a single type and its name says it all
  • The module is private/internal

9. // SAFETY: Comments on Unsafe Blocks

Source:

2,463 // SAFETY: comments in library/.

// library/core/src/slice/mod.rs
unsafe {
    // SAFETY: the caller must uphold the safety contract for `get_unchecked`.
    &*self.as_ptr().add(index)
}

Why

Every unsafe { } block must explain WHY it's sound. The comment justifies that the invariants required by the unsafe operation are upheld at this specific call site. This is how you audit unsafe code.

When to Use

Triggers:

  • Every unsafe { } block (no exceptions in the stdlib)
  • The comment explains the specific proof of soundness HERE
  • Not just "we checked" but "because X guarantees Y which means Z"

Example — before:

unsafe {
    ptr::copy_nonoverlapping(src, dst, count);
}
// Reviewer: "Is this actually safe? Who knows?"

Example — after:

// SAFETY: `src` and `dst` are both derived from the same allocation
// and are non-overlapping because `src` points to [0..mid] and `dst`
// points to [mid..len]. `count` is bounded by `len - mid` which we
// checked above with the assert.
unsafe {
    ptr::copy_nonoverlapping(src, dst, count);
}

When NOT to Use

Don't use this when:

  • There's no unsafe block
  • The safety is already explained by a # Safety doc comment on the enclosing function (but STILL add the inline comment)

10. #[doc(hidden)] for Internal API

Source:

library/std/src/lib.rs

#[doc(hidden)]
#[unstable(feature = "std_internals", issue = "none")]
pub mod macros;

Why

#[doc(hidden)] keeps items out of the generated documentation. The item is still public (needed for macro expansion or cross-crate use) but not part of the documented API. Users shouldn't depend on it.

When to Use

Triggers:

  • Item must be pub for technical reasons (macros, codegen) but isn't meant for users
  • Implementation details exposed by necessity
  • Deprecated items you want to hide but not remove yet

Example — before:

// This is public but users shouldn't use it
pub fn __internal_realloc(ptr: *mut u8, size: usize) -> *mut u8 { ... }
// Shows up in rustdoc — confuses users

Example — after:

/// Internal reallocation helper. Not part of the public API.
#[doc(hidden)]
pub fn __internal_realloc(ptr: *mut u8, size: usize) -> *mut u8 { ... }
// Hidden from rustdoc but still compilable by macros that need it

When NOT to Use

Don't use this when:

  • You can make the item pub(crate) instead (preferred)
  • The item genuinely should be documented for users
  • You're hiding items to avoid documenting them (lazy)

Summary: Documentation Decision Tree

Is the item public?
├── NO → Regular comments (//) for complex logic only
└── YES → Doc comment (///) required
    ├── What sections to include?
    │   ├── Always: Summary line + # Examples
    │   ├── If can panic: # Panics
    │   ├── If returns Result: # Errors
    │   ├── If unsafe: # Safety
    │   └── If related items exist: intra-doc links
    ├── Is it a module?
    │   └── Use //! at the top (module-level docs)
    └── Doc test attributes?
        ├── Normal example: ```
        ├── Shows a panic: ```should_panic
        ├── Shows type error: ```compile_fail
        ├── Can't run (I/O): ```no_run
        └── Last resort: ```ignore
Section When Example Header
Summary Always First line of ///
# Examples Always (public API) /// # Examples
# Safety unsafe fn or unsafe trait /// # Safety
# Panics Function can panic /// # Panics
# Errors Returns Result /// # Errors
// SAFETY: Inside unsafe {} block // SAFETY: ...
#[doc(hidden)] Public but internal Attribute
//! Module/crate docs Inner doc comment

See also: