Documentation
Writing High-Quality API Documentation
High-quality Rust API documentation follows the conventions established by the Rust standard library and the official Rust API Guidelines (C-EXAMPLE, C-FAILURE, C-LINK): every public module, type, and function opens with a concise third-person summary line, explains edge cases and invariants without repeating the type signature, and links related concepts into a cohesive navigation graph.
Anatomy of a Complete Rust Doc Comment:
1. Summary Line: Single sentence in 3rd-person present tense ("Returns...", "Parses...")
(Must fit cleanly in module index tables!)
2. Body Prose: When to use this item, behavior on edge cases, complexity, invariants
3. # Errors: Exact conditions returning `Err(E)` (with intra-doc links to variants)
4. # Panics: Exact conditions causing a panic (with link to non-panicking `try_*`)
5. # Safety: Exact preconditions required by `unsafe fn` / `unsafe trait`
6. # Examples: Runnable, focused code block verified by `cargo test --doc`
Rust API Documentation Style Guidelines
| Guideline | Avoid (Anti-Pattern) | Prefer (Standard Rust Style) |
|---|---|---|
| Opening verb tense | "Return the length" (imperative) or "This function returns..." (wordy) |
"Returns the number of elements in the buffer." (third-person indicative) |
| Summary paragraph length | A 5-sentence wall of text in the first paragraph | A single concise sentence, followed by a blank /// line before details |
| Restating signatures | "Takes a &str called s and returns a Result<u32, ParseIntError>." |
Explain meaning and units ("Parses a timeout duration in milliseconds.") |
| Cross-referencing | Unlinked plain text "See RingBuffer::pop" |
Clickable intra-doc link "See [`RingBuffer::pop`]." |
| Units & Ranges | "Sets the timeout" |
"Sets the read timeout in milliseconds (1..=60_000)." |
Complete Example: A Documented Module Following All Guidelines
#![warn(missing_docs)]
#![warn(clippy::missing_errors_doc)]
#![warn(clippy::missing_panics_doc)]
#![deny(rustdoc::broken_intra_doc_links)]
//! Fixed-capacity ring buffer for bounded telemetry queues.
//!
//! Use [`RingBuffer`] when you need $O(1)$ FIFO insertion and removal with a strict
//! upper bound on allocated items.
use std::collections::VecDeque;
/// Error returned when pushing into a full [`RingBuffer`].
#[derive(Debug, PartialEq, Eq)]
pub struct FullError<T>(
/// The item that could not be inserted because the buffer was at capacity.
pub T,
);
/// A bounded FIFO queue backed by a pre-allocated [`VecDeque`].
///
/// # Examples
///
/// ```
/// # use my_crate::{RingBuffer, FullError};
/// let mut ring = RingBuffer::with_capacity(2);
/// ring.try_push(10).unwrap();
/// ring.try_push(20).unwrap();
///
/// // Pushing beyond capacity returns `FullError` with the rejected item:
/// assert_eq!(ring.try_push(30), Err(FullError(30)));
/// assert_eq!(ring.pop(), Some(10));
/// ```
#[derive(Debug)]
pub struct RingBuffer<T> {
buf: VecDeque<T>,
capacity: usize,
}
impl<T> RingBuffer<T> {
/// Creates an empty [`RingBuffer`] capable of holding up to `capacity` elements.
///
/// # Panics
///
/// Panics if `capacity == 0`.
#[track_caller]
pub fn with_capacity(capacity: usize) -> Self {
assert!(capacity > 0, "RingBuffer capacity must be greater than 0");
Self {
buf: VecDeque::with_capacity(capacity),
capacity,
}
}
/// Appends `item` to the back of the buffer if space is available.
///
/// Runs in $O(1)$ time and never reallocates memory.
///
/// # Errors
///
/// Returns [`Err`]`(`[`FullError`]`(item))` if `self.len() == self.capacity()`,
/// returning ownership of `item` back to the caller.
pub fn try_push(&mut self, item: T) -> Result<(), FullError<T>> {
if self.buf.len() == self.capacity {
return Err(FullError(item));
}
self.buf.push_back(item);
Ok(())
}
/// Removes and returns the oldest element from the front of the buffer,
/// or [`None`] if the buffer is empty.
pub fn pop(&mut self) -> Option<T> {
self.buf.pop_front()
}
/// Returns the number of elements currently stored in the buffer.
pub fn len(&self) -> usize {
self.buf.len()
}
/// Returns `true` if the buffer contains no elements.
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
/// Returns the maximum number of elements the buffer can hold.
pub fn capacity(&self) -> usize {
self.capacity
}
}
fn main() {
let mut ring = RingBuffer::with_capacity(2);
ring.try_push("event-1").unwrap();
ring.try_push("event-2").unwrap();
println!(
"Ring len={}/{}, popped={:?}",
ring.len(),
ring.capacity(),
ring.pop()
);
}
Ring len=2/2, popped=Some("event-1")