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)].
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:
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:
- Examples are always correct (they compile and pass)
- Breaking changes break doc tests (forces updates)
- 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 orno_runinstead)
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
unsafeblock 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
unsafekeyword) - 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!(), orassert!() - 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::Errorwith standard kinds) - There's only one possible error and it's obvious from the name
6. Intra-Doc Links ([Type::method])
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 paniccompile_fail: Showing that the type system prevents misuseno_run: Example requires network, file system, or blocks foreverignore: 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
ignoreto 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.rsorlib.rsfile - 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
# Safetydoc comment on the enclosing function (but STILL add the inline comment)
10. #[doc(hidden)] for Internal API
Source:
#[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
pubfor 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:
- error-handling.md — Error types and Result docs
- unsafe-patterns.md — Safety contracts
- api-design.md — What to document publicly