Documentation with rustdoc
Prerequisites: Building command-line apps
Rust ships with rustdoc, a tool that turns special comments into an HTML documentation site — the same format used for the standard library and for every crate on docs.rs. Documentation comments are written in Markdown.
///documents the item that follows it: a function, struct, field, enum variant, trait, module and so on.//!documents the item that contains it. At the top ofsrc/lib.rsit describes the whole crate; at the top of a module file, the module.- Only public items appear in the generated docs by default.
Syntax
//! Utilities for working with temperatures.
/// Converts degrees Celsius to degrees Fahrenheit.
pub fn to_fahrenheit(celsius: f64) -> f64 {
celsius * 9.0 / 5.0 + 32.0
}
cargo doc --open # build docs for your crate and its dependencies, open them in a browser
cargo doc --no-deps # build docs for your own crate only
cargo test --doc # run only the code examples inside the docs
Conventional sections
By convention a doc comment starts with a one-line summary, then optional headed sections:
# Examples— how to use the item. Almost every public item should have one.# Errors— for functions returningResult: when they returnErr.# Panics— the conditions under which the function panics.# Safety— forunsafefunctions: what the caller must guarantee.
Wrap an item name in backticks and square brackets, like [`Stack::pop`],
to create a link to its documentation (an intra-doc link).
Examples are tests
Code blocks inside doc comments are compiled and run by cargo test as doc
tests. This keeps examples honest: if an API changes and an example stops
working, the test suite fails. Each example is compiled as a separate small
program that uses your crate by name, so it can only use public items. Lines
beginning with # are run but hidden from the rendered documentation, which
is handy for setup code.
Example
src/lib.rs of a library package named stack:
//! A tiny generic stack.
//!
//! Create one with [`Stack::new`], add items with [`Stack::push`] and remove
//! them with [`Stack::pop`].
/// A last-in, first-out collection.
///
/// # Examples
///
/// ```
/// use stack::Stack;
///
/// let mut s = Stack::new();
/// s.push(1);
/// s.push(2);
/// assert_eq!(s.pop(), Some(2));
/// assert_eq!(s.len(), 1);
/// ```
#[derive(Debug, Default)]
pub struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
/// Creates an empty stack.
pub fn new() -> Self {
Stack { items: Vec::new() }
}
/// Pushes an item onto the top of the stack.
pub fn push(&mut self, item: T) {
self.items.push(item);
}
/// Removes and returns the top item, or `None` if the stack is empty.
pub fn pop(&mut self) -> Option<T> {
self.items.pop()
}
/// Returns the number of items on the stack.
pub fn len(&self) -> usize {
self.items.len()
}
/// Returns `true` if the stack holds no items.
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
/// Returns a reference to the top item without removing it.
///
/// # Panics
///
/// Panics if the stack is empty. Use [`Stack::pop`] or check
/// [`Stack::is_empty`] first if that is possible.
///
/// # Examples
///
/// ```
/// # use stack::Stack;
/// let mut s = Stack::new();
/// s.push("top");
/// assert_eq!(*s.peek(), "top");
/// ```
///
/// ```should_panic
/// # use stack::Stack;
/// let s: Stack<i32> = Stack::new();
/// s.peek(); // panics
/// ```
pub fn peek(&self) -> &T {
self.items.last().expect("peek called on an empty stack")
}
}
Run the examples with cargo test --doc.
Expected output
running 3 tests
test src\lib.rs - Stack (line 10) ... ok
test src\lib.rs - Stack<T>::peek (line 59) ... ok
test src\lib.rs - Stack<T>::peek (line 66) ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.55s
The path separator (src/lib.rs on Linux and macOS), the order of the lines
and the timing vary by platform and run. cargo doc --open shows the crate
summary from //!, the Stack page with its example, and each method with
its sections, with the # use lines hidden.
Other code block options
```no_run— compile the example but don't run it (for code that touches the network or the file system).```ignore— don't compile it at all. Use sparingly; an example that isn't checked soon goes stale.```text— not Rust; shown as-is and never tested.
To catch undocumented public items, add #![warn(missing_docs)] at the top of
src/lib.rs.
Common mistakes
- Using
//or/* */and expecting it in the docs. Only///,//!(and their block forms/** */,/*! */) are documentation. - Putting
//!in the middle of a file. Inner doc comments must come before any items in the file or module, otherwise the compiler reportserror[E0753]: expected outer doc comment. - Forgetting
use your_crate::...inside an example. Doc tests are separate programs, so the example fails witherror[E0433]: failed to resolve: use of undeclared type. Add the import, hidden with#if it clutters the docs. - Writing examples for a binary-only package. Doc tests are run for library
crates; put reusable code in
src/lib.rs. - Writing a summary line that just repeats the name ("Stack: a stack"). Say what it does and when to use it.
Practice
Document the function below. Add a one-line summary, a # Errors section, and
an # Examples section with one example that succeeds and one that shows the
error case with assert!(...is_err()). Assume the library crate is named
textkit.
pub fn parse_percent(text: &str) -> Result<u8, String> {
let value: u8 = text.trim_end_matches('%').parse().map_err(|_| format!("not a number: {text}"))?;
if value > 100 {
return Err(format!("out of range: {value}"));
}
Ok(value)
}
Show solution
/// Parses a percentage such as `"42%"` or `"42"` into a number from 0 to 100.
///
/// # Errors
///
/// Returns `Err` with a description if the text is not a whole number, or if
/// the number is greater than 100.
///
/// # Examples
///
/// ```
/// use textkit::parse_percent;
///
/// assert_eq!(parse_percent("42%"), Ok(42));
/// assert_eq!(parse_percent("100"), Ok(100));
/// assert!(parse_percent("150%").is_err());
/// assert!(parse_percent("lots").is_err());
/// ```
pub fn parse_percent(text: &str) -> Result<u8, String> {
let value: u8 = text.trim_end_matches('%').parse().map_err(|_| format!("not a number: {text}"))?;
if value > 100 {
return Err(format!("out of range: {value}"));
}
Ok(value)
}
cargo test --doc then reports test result: ok. 1 passed; ....